User Account Protection was the preliminary name for a core security component of Windows Vista. The component has now been officially named User Account Control (UAC).
security featureUser Account Control: programs which want to be run with
administrativerights need to ask the user for consent.
This made some (really: a minority of) users quite angry: although
these (rather braindead) users continued to abuse
the administrative user account created during Windows
setup for their daily work (instead to follow best practice
and use a limited or standard user account), they had to answer a
prompt whenever they wanted to perform an administrative task.
Unfortunately Microsoft heard these users and weakened
the security feature
: Windows 7 introduced
auto elevation
and enabled it for some 70 programs shipped
with Windows 7 and later versions, which don’t
prompt for consent any more.
Due to flaws in the design and deficiencies in the implementation of
User Account Control, this security feature
(really: security theatre) can be bypassed
trivially in numerous ways when auto elevation
is enabled. As
result, arbitrary programs can then be run with
administrative
rights without prompting the user for
consent.
To defeat these trivial bypasses, auto elevation
must be
disabled by moving the slider of the
User Account Control setting to its highest position
titled Always notify
, as documented and shown in the
MSKB
articles
975787
and
4462938.
The slider position shown by the graphical user interface but does
not always match the effective setting: it shows
Always notify
even if the default setting
Notify me only when programs try to make changes to my computer
is configured!
Log on to the user account created during Windows setup.
Start one of the programs which have auto elevation
enabled,
MMC.exe
or
WUSA.exe
for example: they start without prompting for consent.
Open Control Panel, then User Accounts and
click Change User Account Control setting
: move the slider to
its highest position titled Always notify
and click the
button to apply the changed setting.
Run the command line
"%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\GPEdit.msc"
to start the Local Group Policy Editor snap-in of the
Microsoft Management Console, or run
the command line
"%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\SecPol.msc"
to start the Local Security Policies snap-in, answer
the prompt for consent, then open the Local Policies
folder
and the Security Options
subfolder below: the policy
User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode
is shown as Prompt for consent on the secure desktop
,
properly matching the setting changed in step 3.
Repeat step 2.: both programs prompt for consent now.
Start RegEdit.exe
, answer the
prompt for consent, then open the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
and delete the DWORD
registry entry
ConsentPromptBehaviorAdmin
present there.
Repeat step 4.: the policy
User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode
is now properly shown as Not Defined
.
Open Control Panel, then User Accounts
and click Change User Account Control setting
: the slider
is still shown in its highest position.
Repeat step 2.: both programs don’t
prompt for consent any more, despite the unchanged
slider position Always notify
!
setting, but abuses a registry entry reserved for a
policyinstead, it misinterprets the default policy value "Not Defined" and violates the now 25 year old Designed for Windows guidelines!
[HKEY_CURRENT_USER\Software\‹company name›\‹program name›]
"‹setting›"=…
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\‹program name›]
"‹setting›"=…
[HKEY_CURRENT_USER\Software\Policies\‹company name›\‹program name›]
"‹policy›"=…
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\‹program name›]
"‹policy›"=…
[HKEY_LOCAL_MACHINE\SOFTWARE\‹company name›\‹program name›]
"‹setting›"=…
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\‹program name›]
"‹setting›"=…
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\‹company name›\‹program name›]
"‹policy›"=…
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\‹program name›]
"‹policy›"=…
Every process has an environment block that contains a set of environment variables and their values. There are two types of environment variables: user environment variables (set for each user) and system environment variables (set for everyone).By default, a child process inherits the environment variables of its parent process. […]
[…] To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, […]
Environment variables specify search paths for files, directories for temporary files, application-specific options, and other similar information. The system maintains an environment block for each user and one for the computer. The system environment block represents environment variables for all users of the particular computer. A user's environment block represents the environment variables the system maintains for that particular user, including the set of system environment variables.Both articles but fail to tell that two kinds of user environment variables exist, permanent and volatile, where they are stored, and how to add or modify them: permanent user environment variables are stored in the registry keyBy default, each process receives a copy of the environment block for its parent process. Typically, this is the environment block for the user who is logged on. […]
HKEY_CURRENT_USER\Environment
alias
HKEY_USERS\‹security identifier›\Environment
,
while volatile user environment variables are stored in the
(volatile) registry key
HKEY_CURRENT_USER\Volatile Environment
alias
HKEY_USERS\‹security identifier›\Volatile Environment
,
where they are created during user logon and discarded when the user
logs off.
The articles also fail to tell that not all system environment
variables are stored in the registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
:
the system environment variables ALLUSERSPROFILE
,
COMPUTERNAME
, PUBLIC
,
CommonProgramFiles
,
CommonProgramFiles(x86)
,
CommonProgramW6432
,
ProgramData
, ProgramFiles
,
ProgramFiles(x86)
, ProgramW6432
,
SystemDrive
and SystemRoot
are created
programmatically.
And they fail to tell that system environment variables are obscured by user environment variables of the same name – with but two notable exceptions:
Path
are assigned to the process᾿
environment variable Path
during user logon;
NT AUTHORITY\SYSTEM
alias
LocalSystem
get the system environment variables TEMP
and
TMP
instead of the respective user environment
variables!
Thanks
to the braindead (mis)behaviour
listed last, privileged processes running under the user account
NT AUTHORITY\SYSTEM
alias
LocalSystem
use the unsafe user-writable directory
%SystemRoot%\Temp\
instead of their private and
safe directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
,
allowing unprivileged users to tamper with (executable) files
created there by these privileged processes, eventually resulting in
local escalation of privilege.
Note: see the Security Advisory ADV170017 for just one example of such a vulnerability.
%SystemRoot%\Profiles\%USERNAME%\
into new directories
%SystemDrive%\Documents and Settings\%USERNAME%\
.
NT AUTHORITY\SYSTEM
alias
LocalSystem
in the new directory
%SystemRoot%\System32\Config\SystemProfile\
.
Note: its location was a rather braindead
choice, as it is subject to
file system redirection
on 64-bit editions of Windows NT, where two separate
directories
%SystemRoot%\System32\Config\SystemProfile\
and
%SystemRoot%\SysWoW64\Config\SystemProfile\
exist!
The world-writable Temp
directory
%SystemRoot%\Temp\
, shared by all users in previous
versions of Windows NT, was replaced with separate
private Temp
directories
%USERPROFILE%\Local Settings\Temp\
alias
%SystemDrive%\Documents and Settings\%USERNAME%\Local Settings\Temp\
located within the user profiles – except for
the
LocalSystem
user account, which continued (and still continues) to use the
(still world-writable) directory %SystemRoot%\Temp\
!
Windows XP added the (unprivileged) user accounts
NT AUTHORITY\LOCAL SERVICE
alias
LocalService
and NT AUTHORITY\NETWORK SERVICE
alias
NetworkService
,
placed their user profiles in the directories
%SystemDrive%\Documents and Settings\LocalService\
and
%Systemdrive%\Documents and Settings\NetworkService\
,
set their user environment variables TEMP
and
TMP
to %USERPROFILE%\Local Settings\Temp
,
and created a private Temp
directory within both user
profiles.
Windows Vista relocated these two service profiles to
the new directories
%SystemRoot%\ServiceProfiles\LocalService\
and
%SystemRoot%\ServiceProfiles\NetworkService\
, relocated
all normal user profiles
%SystemDrive%\Documents and Settings\%USERNAME%\
to
the directories %SystemDrive%\Users\%USERNAME%\
, and
kept the profile
%SystemRoot%\System32\Config\SystemProfile\
.
All user accounts except
LocalSystem
kept their private Temp
directory, now
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemDrive%\Users\%USERNAME%\AppData\Local\Temp\
for
the normal user accounts and
%SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Temp\
respectively
%SystemRoot%\ServiceProfiles\NetworkService\AppData\Local\Temp\
for the service user accounts.
At least since Windows 7 the user environment variables
TEMP
and TMP
are set in the
LocalSystem
user account too, despite the directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
is missing in its user profile!
The MSDN article Profiles Directory provides additional information.
Create the text file quirk2.vbs
with the following
content in an arbitrary directory:
' Copyright © 1999-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
Option Explicit
With WScript.CreateObject("WScript.Shell")
WScript.Echo "Environment Variables"
Dim strScope
For Each strScope In Array("PROCESS", "SYSTEM", "USER", "VOLATILE")
WScript.Echo
WScript.Echo "Scope '" & strScope & "': " & .Environment(strScope).Count & " items"
Dim strItem
For Each strItem In .Environment(strScope)
WScript.Echo vbTab & strItem
Next
Next
End With
Execute the
VBScript
quirk2.vbs
created in step 1. under the
LocalSystem
user account to list the environment variables of all scopes:
CScript.exe quirk2.vbs
Microsoft (R) Windows Script Host, Version 5.812 Copyright (C) Microsoft Corporation. All rights reserved. Environment Variables Scope 'PROCESS': 36 items =C:=C:\Windows\System32 =ExitCode=00000000 ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming CommonProgramFiles=C:\Program Files\Common Files CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files CommonProgramW6432=C:\Program Files\Common Files COMPUTERNAME=AMNESIAC ComSpec=C:\Windows\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData HOMEDRIVE=C: HOMEPATH=\Windows\system32 LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=2 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PROMPT=$P$G PSModulePath=%ProgramFiles%\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules PUBLIC=C:\Users\Public SystemDrive=C: SystemRoot=C:\Windows TEMP=C:\Windows\TEMP TMP=C:\Windows\TEMP USERDOMAIN=KANTHAK USERNAME=System USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows Scope 'SYSTEM': 15 items ComSpec=%SystemRoot%\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData OS=Windows_NT Path=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\ PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PSModulePath=%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\ TEMP=%SystemRoot%\TEMP TMP=%SystemRoot%\TEMP USERNAME=SYSTEM windir=%SystemRoot% NUMBER_OF_PROCESSORS=2 PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a Scope 'USER': 3 items PATH=%USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP=%USERPROFILE%\AppData\Local\Temp TMP=%USERPROFILE%\AppData\Local\Temp Scope 'VOLATILE': 0 items
Start the Command Processor under the
LocalSystem
user account, then list (the contents of) the directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
and all environment variables:
DIR "%USERPROFILE%\AppData\Local\Temp" /B SET
File Not Found ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming CommonProgramFiles=C:\Program Files\Common Files CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files CommonProgramW6432=C:\Program Files\Common Files COMPUTERNAME=AMNESIAC ComSpec=C:\Windows\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData HOMEDRIVE=C: HOMEPATH=\Windows\system32 LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=2 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PROMPT=$P$G PSModulePath=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules PUBLIC=C:\Users\Public SystemDrive=C: SystemRoot=C:\Windows TEMP=C:\Windows\TEMP TMP=C:\Windows\TEMP USERDOMAIN=KANTHAK USERNAME=System USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows
Query the registry keys
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
,
HKEY_USERS\S-1-5-18\Environment
and
HKEY_USERS\S-1-5-18\Volatile Environment
to list the
environment variables stored in the registry:
Reg.exe QUERY "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" Reg.exe QUERY "HKEY_USERS\S-1-5-18\Environment" Reg.exe QUERY "HKEY_USERS\S-1-5-18\Volatile Environment"Note: the MSKB article 243300 gives the well-known SID
S-1-5-18
for the
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment ComSpec REG_EXPAND_SZ %SystemRoot%\system32\cmd.exe DriverData REG_SZ C:\Windows\System32\Drivers\DriverData NUMBER_OF_PROCESSORS REG_SZ 2 OS REG_SZ Windows_NT Path REG_EXPAND_SZ %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\ PATHEXT REG_SZ .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE REG_SZ AMD64 PROCESSOR_IDENTIFIER REG_SZ Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL REG_SZ 6 PROCESSOR_REVISION REG_SZ 170a PSModulePath REG_EXPAND_SZ %ProgramFiles%\WindowsPowerShell\Modules;%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules TEMP REG_EXPAND_SZ %SystemRoot%\TEMP TMP REG_EXPAND_SZ %SystemRoot%\TEMP USERNAME REG_SZ SYSTEM windir REG_EXPAND_SZ %SystemRoot% HKEY_USERS\S-1-5-18\Environment Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp TMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp ERROR: The specified registry key or value was not found.
For comparision query the registry keys
HKEY_CURRENT_USER\Environment
and
HKEY_CURRENT_USER\Volatile Environment
of a standard
user account:
Reg.exe QUERY "HKEY_CURRENT_USER\Environment" Reg.exe QUERY "HKEY_CURRENT_USER\Volatile Environment" /S
HKEY_CURRENT_USER\Environment Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp TMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp HKEY_CURRENT_USER\Volatile Environment LOGONSERVER REG_SZ \\AMNESIAC USERDOMAIN REG_SZ AMNESIAC USERNAME REG_SZ Stefan USERPROFILE REG_SZ C:\Users\Stefan HOMEPATH REG_SZ \Users\Stefan HOMEDRIVE REG_SZ C: APPDATA REG_SZ C:\Users\Stefan\AppData\Roaming LOCALAPPDATA REG_SZ C:\Users\Stefan\AppData\Local USERDOMAIN_ROAMINGPROFILE REG_SZ AMNESIAC HKEY_CURRENT_USER\Volatile Environment\1 SESSIONNAME REG_SZ Console CLIENTNAME REG_SZ
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
,
then start the program
SystemPropertiesAdvanced.exe
,
click the button ,
replace the value %SystemRoot%\TEMP
of the system
environment variables TEMP
and TMP
with
%USERPROFILE%\AppData\Local\Temp
, save the changed
setting and restart the system.
Note: a proper implementation calls functions like
GetSystemDirectory()
,
GetSystemWow64Directory()
,
GetWindowsDirectory()
,
SHGetFolderPath()
and
SHGetKnownFolderPath()
to determine (system) paths instead to evaluate (user-controlled)
environment variables!
Write.exe
,
RegEdit.exe
,
NotePad.exe
,
Explorer.exe
as well as
IExplore.exe
and
WordPad.exe
afterwards:
REM Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de> CHDIR /D "%SystemRoot%" FOR /F "Delims==" %? IN ('SET') DO @SET %?= DIR Explorer.exe NotePad.exe RegEdit.exe Write.exe Win.ini /B Write.exe RegEdit.exe /M NotePad.exe /P Win.ini Explorer.exe /E,/Separate,. START IExplore.exe START WordPad.exe EXIT
explorer.exe notepad.exe regedit.exe write.exe win.ini…
For
command of the Command Processor
states:
The documentation for the builtin
- Parsing output
You can use the for /f command to parse the output of a command by making a back-quoted string from the Set between the parentheses. It is treated as a command line, which is passed to a child Cmd.exe. The output is captured into memory and parsed as if it is a file.
Start
command of the Command Processor
states:
- When you run a command that contains the string "CMD" as the first token without an extension or path qualifier, "CMD" is replaced with the value of the COMSPEC variable. This prevents users from picking up cmd from the current directory.
REM Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de> CHDIR /D "%USERPROFILE%" PUSHD "%SystemRoot%" FOR /F "Delims==" %? IN ('SET') DO @SET %?= START /B CMD /C SET SET COMSPEC=%CD%\System32\Reg.exe POPD COPY NUL: Cmd.exe FOR /F "Delims=" %? IN ('PAUSE') DO ECHO %? FOR /F "Delims= UseBackQ" %? IN (`PAUSE`) DO ECHO %? START CMD /C PAUSE START /B ECHO ASSOC | BREAK FTYPE | SHIFT SET | Cmd.exe ERASE Cmd.exe EXIT
COMSPEC=C:\Windows\System32\cmd.exe PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC PROMPT=$P$G 1 File(s) copied. Press any key . . . Access Denied Access Denied ERROR: Invalid Argument/Option - '/c'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - '/K'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - '/S'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - '/S'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - '/S'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - '/S'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - '/S'. Type "REG /?" for usage. Access DeniedOUCH¹: contrary to its documentation,
FOR /F … IN ('…') DO …
as well as
FOR /F "UseBackQ" … IN (`…`) DO …
executes the application determined by the value of the environment
variable COMSPEC
(else the current process’
image) instead of Cmd.exe
as child process.
OUCH²: contrary to its documentation,
START CMD …
does not evaluate the environment
variable COMSPEC
, but executes Cmd.exe
from the CWD
(else the current process᾿ image)!
OUCH³: when run in a pipeline, builtin
commands are executed in a child process determined by the value of
the environment variable COMSPEC
.
The Command Processor sets the environment variables
COMSPEC
, PATHEXT
and PROMPT
if these are not provided by its caller.
COMSPEC
is
a well-known weakness, documented as
CWE-73: External Control of File Name or Path
in the
CWE™,
allowing well-known attacks like
CAPEC-13: Subverting Environment Variable Values
documented in the
CAPEC™.
The (unintended) execution of (a rogue) Cmd.exe
from
the CWD
constitutes a security vulnerability, similar to
CVE-2014-0315
alias
MS14-019
I discovered about 6 years ago, which was fixed with security update
2922229
back then.
Note: the post MS14-019 – Fixing a binary hijacking via .cmd or .bat file on Microsoft’s Security Research and Defense Blog provides additional information.
The well-known underlying weakness is documented as CWE-426: Untrusted Search Path and CWE-427: Uncontrolled Search Path Element, the well-known attack is documented as CAPEC-471: Search Order Hijacking.
They replied with the following statements:
The engineering team has looked over the issue and has stated that this appears to be a documentation error and has been handed over to the team owning that site. In this case this does not appear to be a vulnerability and we will be closing it out on our side. The method of using this information in an attack would require a remote attacker to find malicious program that uses theOUCH: it appears to me that the engineering team is wrong!START cmdand trick the user to trigger this program as a minimum.
Call
command of the Command Processor
states:
Calls one batch program from another without stopping the parent batch program. The call command accepts labels as the target of the call.Note
Call has no effect at the command prompt when it is used outside of a script or batch file.
REM Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de> CALL Reg(Dummy CALL Reg,Dummy CALL Reg;Dummy CALL Reg=Dummy FOR %? IN (First,Second;Third=Fourth) DO @ECHO %? EXIT
ERROR: Invalid Argument/Option - '(Dummy'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - ',Dummy'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - ';Dummy'. Type "REG /?" for usage. ERROR: Invalid Argument/Option - '=Dummy'. Type "REG /?" for usage. First Second Third FourthOUCH¹: the Command Processor mistreats valid filenames containing an opening parenthesis '
(
', a comma ',
', a
semicolon ';
' or an equals sign '=
', and
interprets these characters as separator!
OUCH²: contrary to its documentation,
CALL
does not just call batch scripts and has effect at
the command prompt.
OUCH³: in builtin commands, the
Command Processor mistreats comma, semicolon and equals
sign almost always as delimiter or separator, and sometimes like
white space
.
GetFileMUIInfo()
is documented in the
MSDN as
follows:
The description of its behaviour is but wrong and misleading![…]BOOL GetFileMUIInfo( DWORD dwFlags, PCWSTR pcwszFilePath, PFILEMUIINFO pFileMUIInfo, DWORD *pcbFileMUIInfo );
pFileMUIInfo
[…]
Alternatively, the application can set this parameter to NULL if pcbFileMUIInfo is set to 0. In this case, the function retrieves the required size for the information buffer in pcbFileMUIInfo.
[…]
pcbFileMUIInfo
[…]
Alternatively, the application can set this parameter to 0 if it sets NULL in pFileMUIInfo. In this case, the function retrieves the required file information buffer size in pcbFileMUIInfo. To allocate the correct amount of memory, this value should be added to the size of theFILEMUIINFO
structure itself.
The initial call of
GetFileMUIInfo()
for any module, with address and size of the buffer given as
NULL
and 0, fails (expected) with error 122 alias
ERROR_INSUFFICIENT_BUFFER
,
but always returns 84 as (additional) buffer size;
subsequent calls then return the full buffer size.
The returned (additional or full) buffer size is but not always
sufficient; subsequent calls can fail again with error 122 alias
ERROR_INSUFFICIENT_BUFFER
, so additional calls with the
buffer size returned from the previous call are then necessary until
the call finally succeeds!
Note: implemented properly, the first call would return the correct (full) buffer size and grant a successful second call, as documented (for example) in the MSDN article Retrieving Data of Unknown Length!
Create the text file quirk6.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2009-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
#pragma comment(compiler)
#pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
#pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
#pragma comment(linker, "/ENTRY:wmainCRTStartup")
#pragma comment(linker, "/SUBSYSTEM:CONSOLE")
#pragma comment(user, __TIMESTAMP__)
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
{
WCHAR szBuffer[1025];
DWORD dwBuffer;
DWORD dwConsole;
va_list vaInserts;
va_start(vaInserts, lpFormat);
dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
va_end(vaInserts);
if (dwBuffer == 0)
return FALSE;
if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
return FALSE;
return dwConsole == dwBuffer;
}
const WCHAR szModule[] = L"C:\\Windows\\RegEdit.exe";
__declspec(noreturn)
VOID WINAPI wmainCRTStartup(VOID)
{
PFILEMUIINFO lpFileMUIInfo = NULL;
DWORD dwFileMUIInfo = 0;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (GetFileMUIInfo(MUI_QUERY_CHECKSUM | MUI_QUERY_LANGUAGE_NAME | MUI_QUERY_RESOURCE_TYPES | MUI_QUERY_TYPE,
szModule, lpFileMUIInfo, &dwFileMUIInfo))
PrintConsole(hConsole,
L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
else
{
PrintConsole(hConsole,
L"GetFileMUIInfo() returned error %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
dwFileMUIInfo += sizeof(FILEMUIINFO);
while (dwError == ERROR_INSUFFICIENT_BUFFER)
{
lpFileMUIInfo = LocalAlloc(LPTR, dwFileMUIInfo);
if (lpFileMUIInfo == NULL)
PrintConsole(hConsole,
L"LocalAlloc() returned error %lu\n",
dwError = GetLastError());
else
{
lpFileMUIInfo->dwSize = dwFileMUIInfo;
lpFileMUIInfo->dwVersion = MUI_FILEINFO_VERSION;
if (GetFileMUIInfo(MUI_QUERY_CHECKSUM | MUI_QUERY_LANGUAGE_NAME | MUI_QUERY_RESOURCE_TYPES | MUI_QUERY_TYPE,
szModule, lpFileMUIInfo, &dwFileMUIInfo))
PrintConsole(hConsole,
L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
else
PrintConsole(hConsole,
L"GetFileMUIInfo() returned error %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
if (LocalFree(lpFileMUIInfo) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
GetLastError());
}
}
}
ExitProcess(dwError);
}
Build the application quirk6.exe
from the source file
quirk6.c
created in step 1.:
CL.EXE /GA /GF /Tcquirk6.c /W4 /ZlFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the
MSDN article
Use the Microsoft C++ toolset from the command line
for an introduction.
Note: quirk6.exe
is a pure
Win32 application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk6.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:quirk6.exe quirk6.obj
Execute the application quirk6.exe
built in
step 2. to demonstrate the (mis)behaviour of the
Win32 function
GetFileMUIInfo()
:
.\quirk6.exe
GetFileMUIInfo() returned error 122 and buffer size 84 for module 'C:\Windows\RegEdit.exe' GetFileMUIInfo() returned error 122 and buffer size 166 for module 'C:\Windows\RegEdit.exe' GetFileMUIInfo() returned error 122 and buffer size 180 for module 'C:\Windows\RegEdit.exe' GetFileMUIInfo() returned success 0 and buffer size 180 for module 'C:\Windows\RegEdit.exe'
GetTempPath()
and
GetTempFileName()
are documented in the
MSDN as
follows:
In addition to the[…]DWORD GetTempPath( DWORD nBufferLength, LPTSTR lpBuffer );
The maximum possible return value is MAX_PATH+1 (261).
[…]
The GetTempPath function checks for the existence of environment variables in the following order and uses the first path found:
- The path specified by the TMP environment variable.
- The path specified by the TEMP environment variable.
- The path specified by the USERPROFILE environment variable.
- The Windows directory.
lpBuffer
can be 0 alias NULL
if nBufferLength
is 0.
[…]UINT GetTempFileName( LPCTSTR lpPathName, LPCTSTR lpPrefixString, UINT uUnique, LPTSTR lpTempFileName );
lpPathName
The directory path for the file name. Applications typically specify a period (.) for the current directory or the result of the GetTempPath function. The string cannot be longer than MAX_PATH−14 characters or GetTempFileName will fail.
Create the text file quirk7.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
#pragma comment(compiler)
#pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
#pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
#pragma comment(linker, "/ENTRY:wmainCRTStartup")
#pragma comment(linker, "/SUBSYSTEM:CONSOLE")
#pragma comment(user, __TIMESTAMP__)
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
{
WCHAR szBuffer[1025];
DWORD dwBuffer;
DWORD dwConsole;
va_list vaInserts;
va_start(vaInserts, lpFormat);
dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
va_end(vaInserts);
if (dwBuffer == 0)
return FALSE;
if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
return FALSE;
return dwConsole == dwBuffer;
}
__declspec(noreturn)
VOID WINAPI wmainCRTStartup(VOID)
{
WCHAR szBuffer[MAX_PATH + 2];
DWORD dwBuffer;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwBuffer = GetTempPath(sizeof(szBuffer) / sizeof(*szBuffer), szBuffer);
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetTempPath() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"GetTempPath() returned pathname \'%ls\' of length %lu\n",
szBuffer, dwBuffer);
if (GetTempFileName(szBuffer, L"tmp", 0, szBuffer) == 0)
PrintConsole(hConsole,
L"GetTempFileName() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"GetTempFileName() returned pathname \'%ls\'\n",
szBuffer);
if (!DeleteFile(szBuffer))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu\n",
dwError = GetLastError());
}
}
}
ExitProcess(dwError);
}
Build the application quirk7.exe
from the source file
quirk7.c
created in step 1.:
CL.EXE /GA /GF /Tcquirk7.c /W4 /ZlFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the
MSDN article
Use the Microsoft C++ toolset from the command line
for an introduction.
Note: quirk7.exe
is a pure
Win32 application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk7.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:quirk7.exe quirk7.obj
Create the text file quirk7.cmd
with the following
content in the directory used in the previous steps 1. and 2.:
REM Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
SETLOCAL
CHDIR /D "%ALLUSERSPROFILE%"
SET T
@"%~dpn0.exe"
SET TMP=
@"%~dpn0.exe"
SET TEMP=
@"%~dpn0.exe"
SET USERPROFILE=
@"%~dpn0.exe"
SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
@"%~dpn0.exe"
SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
@"%~dpn0.exe"
SET TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy
@"%~dpn0.exe"
SET TMP=%SystemDrive%\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw
@"%~dpn0.exe"
SET TMP=%SystemDrive%\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
@"%~dpn0.exe"
SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
@"%~dpn0.exe"
SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.
@"%~dpn0.exe"
SET TMP=..
@"%~dpn0.exe"
EXIT /B
Execute the batch script quirk7.cmd
created in
step 3. on Windows 7 (or an earlier version):
.\quirk7.cmd
REM Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de> SETLOCAL CHDIR /D "C:\ProgramData" SET T TEMP=C:\Users\Stefan\AppData\Local\Temp TMP=C:\Users\Stefan\AppData\Local\Temp GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of length 35 GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmp8D8B.tmp' SET TMP= GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of length 35 GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmp8D9B.tmp' SET TEMP= GetTempPath() returned pathname 'C:\Users\Stefan\' of length 16 GetTempFileName() returned pathname 'C:\Users\Stefan\tmp8DAB.tmp' SET USERPROFILE= GetTempPath() returned pathname 'C:\Windows\' of length 11 GetTempFileName() returned pathname 'C:\Windows\tmp8E01.tmp' SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz GetTempPath() returned pathname 'C:\Windows\' of length 11 GetTempFileName() returned pathname 'C:\Windows\tmp8E33.tmp' SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz GetTempPath() returned pathname 'C:\Windows\' of length 11 GetTempFileName() returned pathname 'C:\Windows\tmp8E55.tmp' SET TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145 GetTempFileName() returned error 267 SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145 GetTempFileName() returned error 267 SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv\' of length 130 GetTempFileName() returned error 267 SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145 GetTempFileName() returned error 267 SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. GetTempPath() returned pathname 'C:\ProgramData\' of length 15 GetTempFileName() returned pathname 'C:\ProgramData\tmp8EDD.tmp' SET TMP=.. GetTempPath() returned pathname 'C:\' of length 3 GetTempFileName() returned pathname 'C:\tmp8EFF.tmp' EXIT /BOUCH¹: the environment variables
TMP
, TEMP
and USERPROFILE
are
discarded on Windows 7 and earlier versions if their
value is not less than 130
(= MAX_PATH
÷ 2) characters!
OUCH²: although the directory name is valid,
GetTempFileName()
fails with Win32 error 267 alias
ERROR_DIRECTORY
,
i.e. The directory name is invalid.
instead of error 3 alias
ERROR_PATH_NOT_FOUND
,
i.e. The system cannot find the path specified.
Execute the batch script quirk7.cmd
created in
step 3. on Windows 8 (or a later version):
.\quirk7.cmd
REM Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de> SETLOCAL CHDIR /D "C:\ProgramData" SET T TEMP=C:\Users\Stefan\AppData\Local\Temp TMP=C:\Users\Stefan\AppData\Local\Temp GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of length 35 GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpF791.tmp' SET TMP= GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of length 35 GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpF7A1.tmp' SET TEMP= GetTempPath() returned pathname 'C:\Users\Stefan\' of length 16 GetTempFileName() returned pathname 'C:\Users\Stefan\tmpF7A9.tmp' SET USERPROFILE= GetTempPath() returned pathname 'C:\Windows\' of length 11 GetTempFileName() returned pathname 'C:\Windows\tmpF7B1.tmp' SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz GetTempPath() returned pathname '' of length 277 GetTempFileName() returned pathname '\tmpF7C7.tmp' SET USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\' of length 146 GetTempFileName() returned error 267 SET TEMP=C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy GetTempPath() returned pathname 'C:\ProgramData\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy\' of length 145 GetTempFileName() returned error 267 SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw\' of length 131 GetTempFileName() returned error 267 SET TMP=C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv GetTempPath() returned pathname 'C:\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv\' of length 130 GetTempFileName() returned error 267 SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. GetTempPath() returned pathname '' of length 276 GetTempFileName() returned pathname '\tmpF7D1.tmp' SET TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. GetTempPath() returned pathname 'C:\ProgramData\' of length 15 GetTempFileName() returned pathname 'C:\ProgramData\tmpF7D9.tmp' SET TMP=.. GetTempPath() returned pathname 'C:\' of length 3 GetTempFileName() returned pathname 'C:\tmpF7DF.tmp' EXIT /BOUCH³: contrary to its documentation cited above, the Win32 function
GetTempPath()
returns values greater than MAX_PATH
+ 1!
OUCH: the documentation also fails to tell that the
Win32 function
GetTempFileName()
adds a backslash in front of the filename if the directory path does
not end with a backslash!
GetEnvironmentVariable()
is documented in the
MSDN as
follows:
Note: designed and implemented properly, this function would return the value of[…]DWORD GetEnvironmentVariable( LPCTSTR lpName, LPTSTR lpBuffer, DWORD nSize );
nSize
The size of the buffer pointed to by the lpBuffer parameter, including the null-terminating character, in characters. […]
If the function fails, the return value is zero. If the specified environment variable was not found in the environment block, GetLastError returns ERROR_ENVVAR_NOT_FOUND.
nSize
for failure,
supporting successful retrieval of empty environment variables!
In addition to the quirk bug demonstrated
below, the documentation fails to tell that lpBuffer
can be 0 alias NULL
if nSize
is 0.
It also shows a VERY common mistake: there is no
null-terminating character
, but a
(string-)terminating
!
NUL
character
Create the text file quirk8.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
#pragma comment(compiler)
#pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
#pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
#pragma comment(linker, "/ENTRY:wmainCRTStartup")
#pragma comment(linker, "/SUBSYSTEM:CONSOLE")
#pragma comment(user, __TIMESTAMP__)
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
{
WCHAR szBuffer[1025];
DWORD dwBuffer;
DWORD dwConsole;
va_list vaInserts;
va_start(vaInserts, lpFormat);
dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
va_end(vaInserts);
if (dwBuffer == 0)
return FALSE;
if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
return FALSE;
return dwConsole == dwBuffer;
}
__declspec(noreturn)
VOID WINAPI wmainCRTStartup(VOID)
{
WCHAR szBuffer[MAX_PATH];
DWORD dwBuffer;
DWORD dwCount;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
if (!SetEnvironmentVariable(L"EMPTY", L""))
PrintConsole(hConsole,
L"SetEnvironmentVariable(\"EMPTY\", \"\") returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetEnvironmentVariable(L"EMPTY", szBuffer, 0);
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetEnvironmentVariable(\"EMPTY\", 0x%p, 0) returned error %lu\n",
szBuffer, dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"GetEnvironmentVariable(\"EMPTY\", 0x%p, 0) returned buffer size %lu\n",
szBuffer, dwBuffer);
SetLastError('FAIL');
dwCount = GetEnvironmentVariable(L"EMPTY", szBuffer, dwBuffer);
if (dwCount == 0)
PrintConsole(hConsole,
L"GetEnvironmentVariable(\"EMPTY\", 0x%p, %lu) returned error %lu\n",
szBuffer, dwBuffer, dwError = GetLastError());
}
}
ExitProcess(dwError);
}
Build the application quirk8.exe
from the source file
quirk8.c
created in step 1.:
CL.EXE /GA /GF /Tcquirk8.c /W4 /ZlFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the
MSDN article
Use the Microsoft C++ toolset from the command line
for an introduction.
Note: quirk8.exe
is a pure
Win32 application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk8.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:quirk8.exe quirk8.obj
Execute the application quirk8.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk8.exe
GetEnvironmentVariable("EMPTY", 0x0037F884, 0) returned buffer size 1 GetEnvironmentVariable("EMPTY", 0x0037F884, 1) returned error 1178683724OUCH: for environment variables with empty value, the Win32 function
GetEnvironmentVariable()
returns 0, but fails to (re)set the Win32 error 0 alias
ERROR_SUCCESS
to indicate no error!
GetDllDirectory()
is documented in the
MSDN as
follows:
Note: designed and implemented properly, this function would return the value of[…]DWORD GetDllDirectory( DWORD nBufferLength, LPTSTR lpBuffer );
If the function succeeds, the return value is the length of the string copied to lpBuffer, in characters, not including the terminating null character. If the return value is greater than nBufferLength, it specifies the size of the buffer required for the path.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
nBufferLength
for
failure, supporting successful retrieval of empty strings!
In addition to the quirk bug demonstrated
below, the documentation fails to tell that lpBuffer
can be 0 alias NULL
if nBufferLength
is 0.
Create the text file quirk9.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
#pragma comment(compiler)
#pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
#pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
#pragma comment(linker, "/ENTRY:wmainCRTStartup")
#pragma comment(linker, "/SUBSYSTEM:CONSOLE")
#pragma comment(user, __TIMESTAMP__)
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL PrintConsole(HANDLE hConsole, LPCWSTR lpFormat, ...)
{
WCHAR szBuffer[1025];
DWORD dwBuffer;
DWORD dwConsole;
va_list vaInserts;
va_start(vaInserts, lpFormat);
dwBuffer = wvsprintf(szBuffer, lpFormat, vaInserts);
va_end(vaInserts);
if (dwBuffer == 0)
return FALSE;
if (!WriteConsole(hConsole, szBuffer, dwBuffer, &dwConsole, NULL))
return FALSE;
return dwConsole == dwBuffer;
}
__declspec(noreturn)
VOID WINAPI wmainCRTStartup(VOID)
{
WCHAR szBuffer[MAX_PATH];
DWORD dwBuffer;
DWORD dwCount;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwBuffer = GetDllDirectory(0, szBuffer);
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetDllDirectory(0, 0x%p) returned error %lu\n",
szBuffer, dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"GetDllDirectory(0, 0x%p) returned buffer size %lu\n",
szBuffer, dwBuffer);
SetLastError('FAIL');
dwCount = GetDllDirectory(dwBuffer, szBuffer);
if (dwCount == 0)
PrintConsole(hConsole,
L"GetDllDirectory(%lu, 0x%p) returned error %lu\n",
dwBuffer, szBuffer, dwError = GetLastError());
}
}
ExitProcess(dwError);
}
Build the application quirk9.exe
from the source file
quirk9.c
created in step 1.:
CL.EXE /GA /GF /Tcquirk9.c /W4 /ZlFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the
MSDN article
Use the Microsoft C++ toolset from the command line
for an introduction.
Note: quirk9.exe
is a pure
Win32 application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk9.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:quirk9.exe quirk9.obj
Execute the application quirk9.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk9.exe
GetDllDirectory(0, 0x003EF948) returned buffer size 1 GetDllDirectory(1, 0x003EF948) returned error 1178683724OUCH: when no application-specific DLL search path is set, the Win32 function
GetDllDirectory()
returns 0, but fails to (re)set the Win32 error 0 alias
ERROR_SUCCESS
to indicate no error!
DefWindowProc()
and
PostQuitMessage()
functions are documented in the
MSDN as
follows:
Calls the default window procedure to provide default processing for any window messages that an application does not process. This function ensures that every message is processed. DefWindowProc is called with the same parameters received by the window procedure.
Indicates to the system that a thread has made a request to terminate (quit). It is typically used in response to a WM_DESTROY message.The
WM_NCCREATE
message is documented in the
MSDN as
follows:
Sent prior to the WM_CREATE message when a window is first created.A window receives this message through its WindowProc function.
[…]
If an application processes this message, it should return TRUE to continue creation of the window. If the application returns FALSE, the CreateWindow or CreateWindowEx function will return a NULL handle.
Create the text file quirk10.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
#pragma comment(compiler)
#pragma comment(linker, "/DEFAULTLIB:KERNEL32.LIB")
#pragma comment(linker, "/DEFAULTLIB:USER32.LIB")
#pragma comment(linker, "/ENTRY:wmainCRTStartup")
#pragma comment(linker, "/SUBSYSTEM:CONSOLE")
#pragma comment(user, __TIMESTAMP__)
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
LRESULT WINAPI WindowProc(HWND hWindow,
UINT uMessage,
WPARAM wParam,
LPARAM lParam)
{
switch (uMessage)
{
#if QUIRKS == 1 // DefWindowProc() must be called in response to the
// WM_NCCREATE message to register the window title!
case WM_NCCREATE:
return (LRESULT) TRUE;
#endif
case WM_DESTROY:
PostQuitMessage(0);
// case WM_NULL:
// case WM_NCDESTROY:
// case WM_CREATE:
return (LRESULT) 0;
default:
return DefWindowProc(hWindow, uMessage, wParam, lParam);
}
}
extern const IMAGE_DOS_HEADER __ImageBase;
__declspec(noreturn)
VOID WINAPI wmainCRTStartup(VOID)
{
DWORD dwError;
LRESULT lResult;
BOOL bResult;
HWND hWindow;
MSG msg;
WNDCLASSEX wce = {sizeof(WNDCLASSEX),
CS_GLOBALCLASS,
#if QUIRKS == 2 // DefWindowProc() does not call PostQuitMessage()
// in response to the WM_DESTROY message!
DefWindowProc,
#else
WindowProc,
#endif
0,
0,
(HINSTANCE) &__ImageBase,
(HICON) NULL,
(HCURSOR) NULL,
(HBRUSH) COLOR_BACKGROUND,
(LPCWSTR) NULL,
L"Quirks Demonstration Class",
(HICON) NULL};
ATOM atom = RegisterClassEx(&wce);
if (atom == 0)
dwError = GetLastError();
else
{
hWindow = CreateWindowEx(WS_EX_APPWINDOW,
wce.lpszClassName,
L"Quirks Demonstration Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
(HWND) NULL,
(HMENU) NULL,
wce.hInstance,
NULL);
if (hWindow == NULL)
dwError = GetLastError();
else
{
while ((bResult = GetMessage(&msg, (HWND) NULL, 0, 0)) > 0)
{
if (TranslateMessage(&msg))
;
lResult = DispatchMessage(&msg);
}
dwError = bResult < 0 ? GetLastError() : msg.wParam;
}
if (!UnregisterClass(wce.lpszClassName, wce.hInstance))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Build the application quirk10.exe
from the source file
quirk10.c
created in step 1. with the preprocessor
macro QUIRKS
defined as 2:
CL.EXE /DQUIRKS=2 /GA /GF /Tcquirk10.c /W4 /ZlFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the
MSDN article
Use the Microsoft C++ toolset from the command line
for an introduction.
Note: quirk10.exe
is a pure
Win32 application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk10.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:quirk10.exe quirk10.obj
Start the application quirk10.exe
built in step 2.
and close its window to demonstrate the first (mis)behaviour:
.\quirk10.exeOUCH¹: although the application window titled
Quirks Demonstration Windowwas closed, the Command Processor waits for the child process to terminate … until you press Ctrl C!
Contrary to its documentation cited above, the Win32
function
DefWindowProc()
fails to provide the typical response to the
WM_DESTROY
message, i.e. it does not call
PostQuitMessage()
to terminate the
message loop
running in the primary thread of the process!
Build the application quirk10.exe
from the source file
quirk10.c
created in step 1. again, now with the
preprocessor macro QUIRKS
defined as 1:
CL.EXE /DQUIRKS=1 /GA /GF /Tcquirk10.c /W4 /Zl
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk10.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:quirk10.exe quirk10.obj
Start the application quirk10.exe
built in step 4.
to demonstrate the second (mis)behaviour:
.\quirk10.exe
Quirks Demonstration Window!
Contrary to the documentation cited above, an application’s
WindowProc()
must not return TRUE
in response to
the
WM_NCCREATE
message, but needs to call the
DefWindowProc()
function instead!
An application manifest is an XML file that describes and identifies the shared and private side-by-side assemblies that an application should bind to at run time. These should be the same assembly versions that were used to test the application. Application manifests may also describe metadata for files that are private to the application.Windows’ module loader but fails withFor a complete listing of the XML schema, see […]
STATUS_SXS_CANT_GEN_ACTCTX
when a valid
XML
encoding US-ASCII
, UTF-7
or
Windows-1252
UTF-8
is used in an
application manifest!
Create the text file quirk11.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2021, Stefan Kanthak <stefan.kanthak@nexgo.de>
#pragma comment(compiler)
#pragma comment(linker, "/ENTRY:wmainCRTStartup")
#pragma comment(linker, "/SUBSYSTEM:CONSOLE")
#pragma comment(user, __TIMESTAMP__)
long wmainCRTStartup(void)
{
return -123456789L;
}
Build the application quirk11.exe
from the source file
quirk11.c
created in step 1.:
CL.EXE /GA /GF /Tcquirk11.c /W4 /ZlFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the
MSDN article
Use the Microsoft C++ toolset from the command line
for an introduction.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk11.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:quirk11.exe quirk11.obj
Start the application quirk11.exe
built in step 2.
to verify its proper function:
.\quirk11.exe ECHO %ERRORLEVEL%
-123456789
Create the text file quirk11.exe.manifest
with the
following content next to the application quirk11.exe
built in step 2.:
<?xml version='1.0' encoding='US-ASCII' standalone='yes' ?>
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1' />
Start the application quirk11.exe
built in step 2.
again to demonstrate the (mis)behaviour:
.\quirk11.exe ECHO %ERRORLEVEL%
The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail. 14001OUCH: although the
application manifest
quirk11.exe.manifest
is perfectly valid, loading of the
application fails with error 14001 alias
ERROR_SXS_CANT_GEN_ACTCTX
!
Remove the (really: deny) execute
permission from the
NTFS
ACL of the text
file quirk11.exe.manifest
created in step 4,
then start the application once more:
ICACLS.EXE /Deny "%USERNAME%":(X) quirk11.exe.manifest .\quirk11.exe ECHO %ERRORLEVEL%
processed file: quirk11.exe.manifest Successfully processed 1 files; Failed processing 0 files Access is denied. 5OUCH: although the text file
quirk11.exe.manifest
is only read, not executed,
loading of the application fails with error 5 alias
ERROR_ACCESS_DENIED
!
Gates particlewith a mass equivalent to some 100 G$.
Use the X.509 certificate to send S/MIME encrypted mail.
Note: email in weird format and without a proper sender name is likely to be discarded!
I dislike
HTML (and even
weirder formats too) in email, I prefer to receive plain text.
I also expect to see your full (real) name as sender, not your
nickname.
I abhor top posts and expect inline quotes in replies.
as iswithout any warranty, neither express nor implied.
cookiesin the web browser.
The web service is operated and provided by
Telekom Deutschland GmbH The web service provider stores a session cookie
in the web
browser and records every visit of this web site with the following
data in an access log on their server(s):