The well-known latin proverb
quod non est in actis, non est in mundo.
(english
What is not in the documents does not exist.
) does not apply
here.
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).How to disable User Account Control (UAC) on Windows Server
security feature(really: security theatre) User Account Control – programs which need or want to be run with administrative privileges and access rights have 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 (privileged)
Protected Administrator
account created during
Windows Setup for their daily work (instead to
follow best practise
and use an unprivileged limited
alias 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
(really:
security nightmare) – Windows 7
introduced auto-elevation
and enabled it for some 55 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 it can be bypassed trivially in
numerous ways with its auto-elevation
(mis)feature enabled.
As result, arbitrary programs can then be run with administrative
privileges and access rights without prompting the user for consent.
To defeat some of 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.
Caveat: the slider position displayed in 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 Protected Administrator
account created during Windows Setup.
Start one of the programs which have auto-elevation
enabled,
for example
NetPlWiz.exe
,
PrintUI.exe
or
WUSA.exe
– they start without to prompt for consent.
On a default installation of Windows 7 or later
versions of Windows NT perform the following 9 simple
steps.
Open Control Panel, then User Accounts and
click Change User Account Control setting
, then move the
slider to its highest position titled Always notify
and click
the button to apply the new 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
execute the command line
"%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\SecPol.msc"
to start the Local Security Policy
snap-in, answer the prompt for consent, then open the
Local Policies
folder and the Security Options
subfolder below it – the policy
User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode
is displayed as Prompt for consent on the secure desktop
,
properly matching the setting applied in step 3.
Repeat step 2. – auto-elevating programs prompt for consent now.
Start the Registry Editor
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 displayed as Not Defined
.
Open Control Panel, then User Accounts
and click Change User Account Control setting
– the
slider is still displayed in its highest position
Always notify
.
Repeat step 2. – despite the unchanged
slider position Always notify
auto-elevating programs
don’t prompt for consent any more!
setting, but abuses a registry entry reserved for a
policyinstead, it misinterprets the default policy value
Not Definedand violates the more than 25 year old Designed for Windows guidelines!
[HKEY_CURRENT_USER\Software\‹company›\‹application›]
"‹setting›"=‹value›
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\‹application›]
"‹setting›"=‹value›
[HKEY_CURRENT_USER\Software\Policies\‹company›\‹application›]
"‹policy›"=‹value›
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\‹application›]
"‹policy›"=‹value›
[HKEY_LOCAL_MACHINE\SOFTWARE\‹company›\‹application›]
"‹setting›"=‹value›
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\‹application›]
"‹setting›"=‹value›
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\‹company›\‹application›]
"‹policy›"=‹value›
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\‹application›]
"‹policy›"=‹value›
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, persistent and volatile, that volatile environment variables obscure persistent environment variables with the same name, how to add, modify or remove them, and where they are stored – persistent 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.
HKEY_CURRENT_USER
The articles also fail to tell that user environment variables obscure system environment variables of the same name – with but four notable exceptions:
LibPath
,
OS2LibPath
and Path
are assigned to the
respective process environment variable during user logon;
NT AUTHORITY\SYSTEM
alias
LocalSystem
get the system environment variables TEMP
and
TMP
instead of the respective user environment
variables!
Thanksto the braindead (mis)behaviour listed last, privileged processes running under the user account
NT AUTHORITY\SYSTEM
alias
LocalSystem
use the public user-writable and therefore unsafe
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.
Both 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
,
PROCESSOR_ARCHITEW6432
, PUBLIC
,
CommonProgramFiles
,
CommonProgramFiles(x86)
,
CommonProgramW6432
,
ProgramData
, ProgramFiles
,
ProgramFiles(x86)
, ProgramW6432
,
SystemDrive
and SystemRoot
are created
programmatically, similar to the volatile user environment variables
created during user logon.
Note: they are but obscured by persistent system environment variables as well as persistent and volatile user environment variables with the same name stored in the registry keys shown above!
And last, both articles fail to mention the immutable dynamic system
environment variables FIRMWARE_TYPE
,
NUMBER_OF_PROCESSORS
, __APPDIR__
and
__CD__
which are nowhere stored and not present in the
process environment block, but evaluated upon expansion.
Note: they are not obscured by regular environment variables of the same name!
The MSDN article WOW64 Implementation Details states:
When a 32-bit process is created by a 64-bit process, or when a 64-bit process is created by a 32-bit process, WOW64 sets the environment variables for the created process as shown in the following table.
Process Environment variables 64-bit process PROCESSOR_ARCHITECTURE=AMD64 or PROCESSOR_ARCHITECTURE=IA64 or PROCESSOR_ARCHITECTURE=ARM64
ProgramFiles=%ProgramFiles%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles%
CommonProgramW6432=%CommonProgramFiles%
Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: The ProgramW6432 and CommonProgramW6432 environment variables were added starting with Windows 7 and Windows Server 2008 R2.32-bit process PROCESSOR_ARCHITECTURE=x86
PROCESSOR_ARCHITEW6432=%PROCESSOR_ARCHITECTURE%
ProgramFiles=%ProgramFiles(x86)%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles(x86)%
CommonProgramW6432=%CommonProgramFiles%
%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 – 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 (less privileged) 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 regular user profiles
%SystemDrive%\Documents and Settings\%USERNAME%\
to
the directories %SystemDrive%\Users\%USERNAME%\
, but
kept the profiles
%SystemRoot%\System32\Config\SystemProfile\
and
%SystemRoot%\SysWoW64\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 regular 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\
or
%SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp\
is missing in its user profiles!
The MSDN article Profiles Directory provides additional information.
Create the text file quirk1.vbs
with the following
content in an arbitrary, preferable empty directory:
Rem Copyright © 1999-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("WScript.Shell")
WScript.StdOut.WriteLine "Environment Variables"
For Each strScope In Array("PROCESS", "SYSTEM", "USER", "VOLATILE")
WScript.StdOut.WriteLine
WScript.StdOut.WriteLine "Scope '" & strScope & "': " & .Environment(strScope).Count & " items"
For Each strItem In .Environment(strScope)
WScript.StdOut.WriteLine vbTab & strItem
Next
Next
End With
Execute the
VBScript
quirk1.vbs
created in step 1. under the
LocalSystem
user account to list the environment variables of all scopes:
CSCRIPT.EXE quirk1.vbs
Microsoft (R) Windows Script Host, Version 5.812 Copyright (C) Microsoft Corporation. All rights reserved. Environment Variables Scope 'PROCESS': 31 items 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 LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=4 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 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files 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=AMNESIAC$ 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=4 PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 Scope 'USER': 3 items PATH=%USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP=%USERPROFILE%\AppData\Local\Temp TMP=%USERPROFILE%\AppData\Local\Temp Scope 'VOLATILE': 0 itemsOops: although the user environment variables
TEMP
and TMP
exist, the process
environment variables TEMP
and TMP
were
set from the system environment variables!
Start the Command Processor
Cmd.exe
under the
LocalSystem
user account, then list all environment variables and (the contents
of) the directory %LOCALAPPDATA%\
alias
%USERPROFILE%\AppData\Local\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\
to determine whether a subdirectory Temp\
exists there:
SET DIR /A "%LOCALAPPDATA%"
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 LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=4 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 96 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 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=AMNESIAC$ USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\system32\config\systemprofile\AppData\Local 04/27/2019 08:15 PM <DIR> . 04/27/2019 08:15 PM <DIR> .. 04/27/2019 08:15 PM <DIR> Microsoft 0 File(s) 0 bytes 3 Dir(s) 9,876,543,210 bytes freeOops: a subdirectory
Temp\
does not
exist!
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 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 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% NUMBER_OF_PROCESSORS REG_SZ 4 PROCESSOR_IDENTIFIER REG_SZ Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL REG_SZ 6 PROCESSOR_REVISION REG_SZ 5e03 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.
Query the registry keys HKEY_CURRENT_USER\Environment
and HKEY_CURRENT_USER\Volatile Environment
of a
standard user account for comparison:
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\
(on 64-bit systems
%SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp\
too), 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
settings and reboot the system.
Caveat: on 64-bit systems, the disjoint directories might cause surprising (mis)behaviour!
Create the text file quirk1.cmd
with the following
content in an arbitrary, preferable empty directory:
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
@IF NOT DEFINED SystemRoot EXIT /B
@CHDIR /D "%SystemRoot%"
@IF NOT EXIST "SysWoW64\cmd.exe" EXIT /B
@FOR /F "Delims==" %%? IN ('SET') DO @SET "%%?="
@SET SystemRoot=%CD%
@IF EXIST "SysNative\cmd.exe" (
"SysNative\cmd.exe" /D /C "SET & EXIT"
) ELSE (
"SysWoW64\cmd.exe" /D /C "SET & CALL ""%~f0"" & PAUSE & EXIT"
)
@EXIT /B
On a 64-bit system, start the batch script quirk1.cmd
created in step 1. per double-click:
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> COMSPEC=C:\Windows\SysWoW64\cmd.exe PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC PROMPT=$P$G SystemRoot=C:\Windows REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> COMSPEC=C:\Windows\system32\cmd.exe PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC PROMPT=$P$G SystemRoot=C:\Windows Press any key to continue . . .OOPS: if one of the environment variables
CommonProgramFiles
,
CommonProgramFiles(x86)
, ProgramFiles
,
ProgramFiles(x86)
or
PROCESSOR_ARCHITECTURE
is not present in the
environment block of the parent process, the derived environment
variables CommonProgramFiles
and
CommonProgramW6432
respectively
ProgramFiles
and ProgramW6432
are not set
in 64-bit processes started from a 32-bit process, while in 32-bit
processes started from a 64-bit process the derived environment
variables CommonProgramFiles
,
CommonProgramW6432
, ProgramFiles
,
ProgramW6432
respectively
PROCESSOR_ARCHITEW6432
as well as the inherited
environment variable PROCESSOR_ARCHITECTURE
are
missing!
Start the Command Processor
Cmd.exe
, then execute the
following command lines:
SET NUMBER_OF_PROCESSORS= IF DEFINED NUMBER_OF_PROCESSORS SET NUMBER_OF_PROCESSORS ECHO %NUMBER_OF_PROCESSORS% SET NUMBER_OF_PROCESSORS=quirk SET NUMBER_OF_PROCESSORS ECHO %NUMBER_OF_PROCESSORS% IF DEFINED __APPDIR__ SET __APPDIR__ ECHO %__APPDIR__% SET __APPDIR__=quirk SET __APPDIR__ ECHO %__APPDIR__% IF DEFINED __CD__ SET __CD__ ECHO %__CD__% SET __CD__=quirk SET __CD__ ECHO %__CD__%Note: the command lines can be copied and pasted as block into a Command Processor window.
Environment variable NUMBER_OF_PROCESSORS not defined! 4 NUMBER_OF_PROCESSORS=quirk 4 Environment variable __APPDIR__ not defined! C:\Windows\system32\ __APPDIR__=quirk C:\Windows\system32\ Environment variable __CD__ not defined C:\Users\Stefan\Desktop\ __CD__=quirk C:\Users\Stefan\Desktop\OUCH¹: the undocumented (dynamic) environment variables
NUMBER_OF_PROCESSORS
,
__APPDIR__
and __CD__
are both defined and
not defined – they exhibit a quantum superposition like a
qubit!
OUCH²: a (regular) environment variable
NUMBER_OF_PROCESSORS
, __APPDIR__
or
__CD__
shows two different values – again a
quantum superposition!
NOTE: the undocumented (dynamic) environment
variables FIRMWARE_TYPE
(introduced with
Windows 8), NUMBER_OF_PROCESSORS
,
__APPDIR__
and __CD__
are created
(read-only) deep down in the bowels of
NTDLL.dll
– they are
evaluated by the Win32 functions
ExpandEnvironmentStrings()
,
ExpandEnvironmentStringsForUser()
and
GetEnvironmentVariable()
even if a regular environment variable of the same name exists in
the process’ environment block!
Create the text file quirk1.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szQuirk[] = {L"ALLUSERSPROFILE",
L"APPDATA",
L"CommonAppData",
L"CommonProgramFiles",
L"CommonProgramFiles(x86)",
L"CommonProgramW6432",
L"COMSPEC",
L"DriverData",
L"HOMEDRIVE",
L"HOMEPATH",
L"HOMESHARE",
L"LOCALAPPDATA",
L"OneDrive",
L"PATH",
L"ProgramData",
L"ProgramFiles",
L"ProgramFiles(x86)",
L"ProgramW6432",
L"PSModulePath",
L"PUBLIC",
L"SystemDrive",
L"SystemRoot",
L"TEMP",
L"TMP",
L"USERPROFILE",
L"windir"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError;
DWORD dwQuirk = 0;
LPWSTR lpQuirk;
LPWSTR lpBlock;
HKEY hk;
HANDLE hToken;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
#ifndef QUIRKS
do
if (!SetEnvironmentVariable(szQuirk[dwQuirk], L""))
PrintConsole(hConsole,
L"SetEnvironmentVariable() returned error %lu\n",
dwError = GetLastError());
while (++dwQuirk < sizeof(szQuirk) / sizeof(*szQuirk));
lpBlock = GetEnvironmentStrings();
if (lpBlock == NULL)
PrintConsole(hConsole,
L"GetEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
{
for (lpQuirk = lpBlock;
lpQuirk[0] != L'\0';
lpQuirk[dwQuirk = wcslen(lpQuirk)] = L'\n', lpQuirk += ++dwQuirk)
continue;
if (!WriteConsole(hConsole, lpBlock, dwQuirk = lpQuirk - lpBlock, &dwError, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwError ^= dwQuirk)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!FreeEnvironmentStrings(lpBlock))
PrintConsole(hConsole,
L"FreeEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
}
#else // QUIRKS
#if QUIRKS == 3
dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
#elif QUIRKS == 2
dwError = RegOpenKeyEx(HKEY_CURRENT_USER,
L"Environment",
#elif QUIRKS <= 1
dwError = RegOpenKeyEx(HKEY_CURRENT_USER,
L"Volatile Environment",
#else
#error QUIRKS out of range from 0 to 3!
#endif
REG_OPTION_RESERVED,
KEY_SET_VALUE,
&hk);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"RegOpenKeyEx() returned error %lu\n",
dwError);
else
{
do
{
dwError = RegSetValueEx(hk,
szQuirk[dwQuirk],
0,
REG_SZ,
#if QUIRKS == 0
NULL,
0);
#else
L"",
sizeof(L""));
#endif
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"RegSetValueEx() returned error %lu\n",
dwError);
}
while (++dwQuirk < sizeof(szQuirk) / sizeof(*szQuirk));
dwError = RegCloseKey(hk);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"RegCloseKey() returned error %lu\n",
dwError);
}
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
PrintConsole(hConsole,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CreateEnvironmentBlock(&lpBlock, hToken, FALSE))
PrintConsole(hConsole,
L"CreateEnvironmentBlock() returned error %lu\n",
dwError = GetLastError());
else
{
for (lpQuirk = lpBlock;
lpQuirk[0] != L'\0';
lpQuirk[dwQuirk = wcslen(lpQuirk)] = L'\n', lpQuirk += ++dwQuirk)
continue;
if (!WriteConsole(hConsole, lpBlock, dwQuirk = lpQuirk - lpBlock, &dwError, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwError ^= dwQuirk)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!DestroyEnvironmentBlock(lpBlock))
PrintConsole(hConsole,
L"DestroyEnvironmentBlock() returned error %lu\n",
GetLastError());
}
if (!CloseHandle(hToken))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
#endif // QUIRKS
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk1.c advapi32.lib kernel32.lib user32.lib userenv.libFor 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: quirk1.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c quirk1.c(65) : warning C4101: 'hToken' : unreferenced local variable quirk1.c(64) : warning C4101: 'hk' : unreferenced local variable Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Execute the console application quirk1.exe
built in
step 2. to show proper behaviour first:
.\quirk1.exe
=::=::\ =C:=C:\Users\Stefan\Desktop =ExitCode=00000000 ALLUSERSPROFILE= APPDATA= CL=/GAFy /Oisy /W4 /Zl CommonAppData= CommonProgramFiles= CommonProgramFiles(x86)= CommonProgramW6432= COMPUTERNAME=AMNESIAC ComSpec= DriverData= HOMEDRIVE= HOMEPATH= HOMESHARE= LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE LOCALAPPDATA= LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OneDrive= OS=Windows_NT Path= PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=x86 PROCESSOR_ARCHITEW6432=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData= ProgramFiles= ProgramFiles(x86)= ProgramW6432= PROMPT=$P$G PSModulePath= PUBLIC= SESSIONNAME=Console SystemDrive= SystemRoot= TEMP= TMP= USERDOMAIN=AMNESIAC USERDOMAIN_ROAMINGPROFILE=AMNESIAC USERNAME=Stefan USERPROFILE= windir=Note: (all 26) empty environment variables are present in the process environment block.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
second time, now with the preprocessor macro QUIRKS
defined as 0:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=0 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Export the registry entries from the registry key
HKEY_CURRENT_USER\Volatile Environment
to the file
quirk1.reg
:
REG.EXE EXPORT "HKEY_CURRENT_USER\Volatile Environment" quirk1.reg
The operation completed successfully.
Execute the console application quirk1.exe
built in
step 4. to show the first bug:
.\quirk1.exe
ALLUSERSPROFILE=AMNESIAC APPDATA=Stefan CommonAppData=AMNESIAC CommonProgramFiles=AMNESIAC CommonProgramFiles(x86)=AMNESIAC CommonProgramW6432=AMNESIAC COMPUTERNAME=AMNESIAC ComSpec=AMNESIAC DriverData=AMNESIAC HOMEDRIVE=Stefan HOMEPATH=Stefan HOMESHARE=Stefan LOCALAPPDATA=Stefan LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OneDrive=AMNESIAC OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;AMNESIAC PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=AMNESIAC ProgramFiles=AMNESIAC ProgramFiles(x86)=AMNESIAC ProgramW6432=AMNESIAC PSModulePath=AMNESIAC PUBLIC=AMNESIAC SESSIONNAME=Console SystemDrive=AMNESIAC SystemRoot=AMNESIAC TEMP=AMNESIAC TMP=AMNESIAC USERDOMAIN_ROAMINGPROFILE=AMNESIAC USERPROFILE=Stefan windir=AMNESIACOUCH¹: volatile user environment variables stored without value in the Registry are written to the environment block with the value of arbitrary other now missing environment variables, here
COMPUTERNAME
and USERPROFILE
, and they
obscure programmatic system environment variables!
(Optional) If you are curious, execute the following command line to
signal the (graphical) Shell
Explorer.exe
that environment
variables have changed and watch what happens when you start some
applications:
SETX.EXE QUIRKS ""
SUCCESS: Specified value was saved.Note: when you've seen enough havoc and malfunctions, just log off and log on again, then continue with step 9. – volatile registry keys and their entries don’t survive logoff and shutdown!
Restore the (volatile) registry entries from the file
quirk1.reg
created in step 5.:
REG.EXE DELETE "HKEY_CURRENT_USER\Volatile Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
third time, now with the preprocessor macro QUIRKS
defined as 1:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=1 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Execute the console application quirk1.exe
built in
step 9. to show the second bug:
.\quirk1.exe
COMPUTERNAME=AMNESIAC LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\; PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 SESSIONNAME=Console USERDOMAIN_ROAMINGPROFILE=AMNESIACOUCH²: volatile user environment variables stored with empty value in the Registry are not written to the environment block, and they inhibit the addition of programmatic system environment variables with the same name to the environment block!
Oops¹: the previously present environment
variables USERDOMAIN
and USERNAME
are
missing too!
(Optional) If you are curious, execute the following command line to
signal the (graphical) Shell
Explorer.exe
that environment
variables have changed and watch what happens when you start some
applications:
SETX.EXE QUIRKS ""
SUCCESS: Specified value was saved.Note: when you've seen enough havoc and malfunctions, just log off and log on again, then continue with step 13. – volatile registry keys and their entries don’t survive logoff and shutdown!
Restore the (volatile) registry entries from the file
quirk1.reg
created in step 5.:
REG.EXE DELETE "HKEY_CURRENT_USER\Volatile Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
fourth time, now with the preprocessor macro QUIRKS
defined as 2:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=2 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Export the registry entries from the registry key
HKEY_CURRENT_USER\Environment
to the file
quirk1.reg
:
REG.EXE EXPORT "HKEY_CURRENT_USER\Environment" quirk1.reg /F
The operation completed successfully.
Execute the console application quirk1.exe
built in
step 13. to show the second bug again:
.\quirk1.exe
COMPUTERNAME=AMNESIAC LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\; PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 SESSIONNAME=Console USERDOMAIN_ROAMINGPROFILE=AMNESIACOUCH³: persistent user environment variables stored with empty value in the Registry are not written to the environment block, and they inhibit the addition of programmatic system environment variables with the same name to the environment block!
Oops²: the previously present environment
variables USERDOMAIN
and USERNAME
are
missing too, again!
(Optional) If you are curious, execute the following command line to
signal the (graphical) Shell
Explorer.exe
that environment
variables have changed and watch what happens when you start some
applications, then log off and log on again:
SETX.EXE windir ""
SUCCESS: Specified value was saved.Note: since the Shell won’t start any more, your administrator needs to load your registry hive, then restore the values of the registry entries
PATH
,
TEMP
and TMP
to their default values,
remove the 22 remaining empty registry entries and finally unload
the registry hive before you can continue with step 18.:
REG.EXE LOAD HKU\Quirks "%SystemDrive%\Users\‹account›\NTUSER.DAT" REG.EXE ADD HKU\Quirks\Environment /V PATH /T REG_EXPAND_SZ /D %USERPROFILE^%\AppData\Local\Microsoft\WindowsApps /F REG.EXE ADD HKU\Quirks\Environment /V TEMP /T REG_EXPAND_SZ /D %USERPROFILE^%\AppData\Local\Temp /F REG.EXE ADD HKU\Quirks\Environment /V TMP /T REG_EXPAND_SZ /D %USERPROFILE^%\AppData\Local\Temp /F REG.EXE DELETE HKU\Quirks\Environment /V ALLUSERSPROFILE /F REG.EXE DELETE HKU\Quirks\Environment /V APPDATA /F REG.EXE DELETE HKU\Quirks\Environment /V CommonAppData /F REG.EXE DELETE HKU\Quirks\Environment /V CommonProgramFiles /F REG.EXE DELETE HKU\Quirks\Environment /V CommonProgramFiles(x86) /F REG.EXE DELETE HKU\Quirks\Environment /V CommonProgramW6432 /F REG.EXE DELETE HKU\Quirks\Environment /V COMSPEC /F REG.EXE DELETE HKU\Quirks\Environment /V DriverData /F REG.EXE DELETE HKU\Quirks\Environment /V HOMEDRIVE /F REG.EXE DELETE HKU\Quirks\Environment /V HOMEPATH /F REG.EXE DELETE HKU\Quirks\Environment /V HOMESHARE /F REG.EXE DELETE HKU\Quirks\Environment /V LOCALAPPDATA /F REG.EXE DELETE HKU\Quirks\Environment /V OneDrive /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramData /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramFiles /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramFiles(x86) /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramW6432 /F REG.EXE DELETE HKU\Quirks\Environment /V PSModulePath /F REG.EXE DELETE HKU\Quirks\Environment /V PUBLIC /F REG.EXE DELETE HKU\Quirks\Environment /V SystemDrive /F REG.EXE DELETE HKU\Quirks\Environment /V SystemRoot /F REG.EXE DELETE HKU\Quirks\Environment /V USERPROFILE /F REG.EXE DELETE HKU\Quirks\Environment /V windir /F REG.EXE UNLOAD HKU\Quirks
The operation completed successfully. […] The operation completed successfully.Note: if your registry hive is still loaded under
HKEY_USERS\S-1-5-21-‹number›-‹number›-‹number›-‹number›
,
you or your administrator can repair the registry entries there.
Restore the (persistent) registry entries from the file
quirk1.reg
created in step 14.:
REG.EXE DELETE "HKEY_CURRENT_USER\Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
(Optional) If you are curious, execute the following command line to
set a registry entry with empty value for the user environment
variable SystemRoot
and signal the (graphical)
Shell that environment variables
have changed, then watch what happens when you start some
applications – log off and log on again after you have seen
enough havoc:
SETX.EXE SystemRoot ""
SUCCESS: Specified value was saved.Note: since the Shell won’t start any more, your administrator needs to load your registry hive, then remove the offending empty registry entry
SystemRoot
and finally
unload the registry hive before you can continue with step 18.:
REG.EXE LOAD HKU\Quirks "%SystemDrive%\Users\‹account›\NTUSER.DAT" REG.EXE DELETE HKU\Quirks\Environment /V SystemRoot /F REG.EXE UNLOAD HKU\Quirks
The operation completed successfully. The operation completed successfully. The operation completed successfully.Note: if your registry hive is still loaded under
HKEY_USERS\S-1-5-21-‹number›-‹number›-‹number›-‹number›
,
you or your administrator can repair the registry entries there.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
fifth time, now with the preprocessor macro QUIRKS
defined as 3:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=2 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Export the registry entries from the registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
to the file quirk1.reg
:
REG.EXE EXPORT "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" quirk1.reg /Y
The operation completed successfully.
Execute the console application quirk1.exe
built in
step 18. as administrator to show the second
bug a last time:
.\quirk1.exe
COMPUTERNAME=AMNESIAC LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\; PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 SESSIONNAME=Console USERDOMAIN_ROAMINGPROFILE=AMNESIACOUCH⁴:
persistent
system
environment variables stored with empty value in the
Registry
are not written to the environment block, and they
inhibit the addition of programmatic system environment variables
with the same name to the environment block!
Oops³: the previously present environment
variables USERDOMAIN
and USERNAME
are
missing too!
(Optional) If you are curious, execute the following command line to
signal the Shell
Explorer.exe
that environment
variables have changed and see what happens when you start some
applications, then reboot:
SETX.EXE windir "" /M
SUCCESS: Specified value was saved.
Note: since Windows won’t start
any more, you need to boot
Windows RE, load
the registry hive C:\Windows\System32\Config\SYSTEM
,
restore the values of the overwritten registry entries, remove the
remaining empty registry entries and finally unload the registry
hive before you can boot Windows again:
REG.EXE LOAD "HKU\SYSTEM" "‹drive›:\Windows\System32\Config\SYSTEM"
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ComSpec /T REG_EXPAND_SZ /D %SystemRoot^%\System32\cmd.exe /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V DriverData /T REG_EXPAND_SZ /D %SystemRoot^%\System32\Drivers\DriverData /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V Path /T REG_EXPAND_SZ /D %SystemRoot^%\System32;%SystemRoot^%;%SystemRoot^%\System32\Wbem;%SystemRoot^%\System32\WindowsPowerShell\v1.0
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V PSModulePath /T REG_EXPAND_SZ /D %SystemRoot^%\System32\WindowsPowerShell\v1.0\Modules\ /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V TEMP /T REG_EXPAND_SZ /D %SystemRoot^%\Temp /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V TMP /T REG_EXPAND_SZ /D %SystemRoot^%\Temp /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V windir /T REG_EXPAND_SZ /D %SystemRoot^% /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ALLUSERSPROFILE /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V APPDATA /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonAppData /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonProgramFiles /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonProgramFiles(x86) /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonProgramW6432 /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V HOMEDRIVE /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V HOMEPATH /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V HOMESHARE /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V LOCALAPPDATA /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V OneDrive /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramData /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramFiles /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramFiles(x86) /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramW6432 /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V PUBLIC /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V SystemDrive /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V SystemRoot /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V USERPROFILE /F
REG.EXE UNLOAD "HKU\SYSTEM"
The operation completed successfully. […] The operation completed successfully.
Restore the registry entries from the file quirk1.reg
created in step 19.:
REG.EXE DELETE "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
(Optional) If you are curious, execute the following command line to
set a registry entry with empty value for the system environment
variable SystemRoot
and signal the (graphical)
Shell
Explorer.exe
that environment
variables have changed, then watch what happens when you start some
applications – log off and log on again or reboot after you
have seen enough havoc:
SETX.EXE SystemRoot "" /M
SUCCESS: Specified value was saved.
Note: since Windows won’t start
any more, you need to boot
Windows RE, load
the registry hive C:\Windows\System32\Config\SYSTEM
,
remove the offending empty registry entry SystemRoot
and finally unload the registry hive before you can boot
Windows again:
REG.EXE LOAD "HKU\SYSTEM" "‹drive›:\Windows\System32\Config\SYSTEM" REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V SystemRoot /F REG.EXE UNLOAD "HKU\SYSTEM"
The operation completed successfully. The operation completed successfully. The operation completed successfully.
Note: a proper implementation calls functions like
GetSystemDirectory()
,
GetSystemWindowsDirectory()
,
GetSystemWow64Directory()
,
GetWindowsDirectory()
,
SHGetFolderPath()
and
SHGetKnownFolderPath()
to determine (system) paths instead to evaluate (user-controlled)
environment variables!
Cmd.exe
, then execute the
following command lines to remove all environment variables and
(attempt to) start the applications
Explorer.exe
,
NotePad.exe
,
RegEdit.exe
and
Write.exe
as well as
IExplore.exe
and
WordPad.exe
afterwards:
REM Copyright © 2004-2025, 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 Explorer.exe /E,/Separate,. NotePad.exe /P Win.ini RegEdit.exe /M Write.exe START IExplore.exe START WordPad.exe EXITNote: the command lines can be copied and pasted as block into a Command Processor window.
explorer.exe notepad.exe regedit.exe write.exe win.iniOops: while
NotePad.exe
and
RegEdit.exe
start properly,
Explorer.exe
,
Write.exe
plus
START IExplore.exe
fail silently, and
START WordPad.exe
fails with an error message box!
Create the text file quirk3.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL WINAPI _DllMainCRTStartup(HMODULE hModule,
DWORD dwReason,
LPVOID lpReserved)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule;
WCHAR szProcess[MAX_PATH];
DWORD dwProcess;
WCHAR szMessage[1024];
if (dwReason != DLL_PROCESS_ATTACH)
return FALSE;
dwModule = GetModuleFileName(hModule,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\0';
dwProcess = GetModuleFileName((HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess < sizeof(szProcess) / sizeof(*szProcess))
szProcess[dwProcess] = L'\0';
if (wsprintf(szMessage,
L"\'%ls\' loaded at address 0x%p in process \'%ls\'\n",
szModule, hModule, szProcess) == 0)
return FALSE;
return IDOK == MessageBoxEx(HWND_DESKTOP,
szMessage,
__LPREFIX(__FUNCTION__) L"() entry point",
MB_OKCANCEL,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
}
MAKELANGID
MAKELANGID
Build the
DLL
quirk3.dll
from the source file quirk3.c
created in step 1.:
SET CL=/GAFy /LD /Oisy /W4 /Zl SET LINK=/NODEFAULTLIB CL.EXE quirk3.c kernel32.lib user32.libFor 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: quirk3.dll
is a pure
Win32
DLL
and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk3.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /NODEFAULTLIB /out:quirk3.dll /dll /implib:quirk3.lib quirk3.obj kernel32.lib user32.lib
Create the subdirectory System32\
in the current
directory, create hardlinks
NDFAPI.dll
and
PropSys.dll
of the
DLL
quirk3.dll
built in step 2. in this subdirectory,
set the environment variable SystemRoot
to the current
directory, then execute the two START
commands used
before again:
MKDIR System32 MKLINK /H System32\NDFAPI.dll quirk3.dll MKLINK /H System32\PropSys.dll quirk3.dll SET SystemRoot=%CD% START IExplore.exe START WordPad.exeNote: the command lines can be copied and pasted as block into a Command Processor window.
Hardlink created for System32\NPFAPI.dll <<===>> quirk3.dll Hardlink created for System32\PropSys.dll <<===>> quirk3.dllOUCH: the internal
Start
command of the Command Processor
Cmd.exe
loads and executes
(at least) arbitrary bogus
DLLs
named
NDFAPI.dll
and PropSys.dll
from
an arbitrary bogus system directory!
.bat
and .cmd
are
associated with the
file types
alias
Programmatic Identifiers
batfile
and cmdfile
whose
Open
verbis registered with the command line template
"%1" %*
:
ASSOC .bat ASSOC .cmd FTYPE batfile FTYPE cmdfile
.bat=batfile .cmd=cmdfile batfile="%1" %* cmdfile="%1" %*
ShellExecute()
and
ShellExecuteEx()
retrieve these command line templates, replace the various tokens
%‹digit›
,
%‹letter›
and %*
with file
or path names and arguments, then feed the completed command line to
one of the
CreateProcess()
,
CreateProcessAsUser()
,
CreateProcessWithLogonW()
or
CreateProcessWithTokenW()
functions.
Launching Applications (ShellExecute, ShellExecuteEx, SHELLEXECUTEINFO)
The documentation for the
CreateProcess()
function states:
To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file.Due to the security vulnerability CVE-2014-0315 alias MS14-019 I discovered and reported at the MSRC about 11 years ago, which was fixed with security update 2922229 some months later, the following note was added several years later:
Important
The MSRC engineering team advises against this. See MS14-019 – Fixing a binary hijacking via .cmd or .bat file for more details.
Create the text file quirk4.vbs
with the following
content in an arbitrary, preferable empty directory:
Rem Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("Scripting.FileSystemObject")
Const fsoWindowsFolder = 0
Const fsoSystemFolder = 1
Const fsoTemporaryFolder = 2
strFolder = .GetSpecialFolder(fsoWindowsFolder).Path
strProgram = .BuildPath(strFolder, "NotePad.exe")
End With
With WScript.CreateObject("WScript.Shell").Environment("VOLATILE")
If .Item("COMSPEC") = vbNullString Then
.Item("COMSPEC") = strProgram
Else
.Remove("COMSPEC")
End If
End With
Execute the
VBScript
quirk4.vbs
created in step 1. per double-click to
set the volatile environment variable COMSPEC
to the
path name of the Editor.
Create the text files quirk4.bat
and
quirk4.cmd
with the following content in an arbitrary,
preferable empty directory:
PAUSE
Execute the batch scripts quirk4.bat
and
quirk4.cmd
created in step 3. per double-click.
OUCH: the Editor
starts instead of the Command Processor
Cmd.exe
and displays an error
message box – most obviously Microsoft’s
developers and their quality miserability
assurance ignore their own companies’ documentation and
(security) guidelines!
Execute the
VBScript
quirk4.vbs
created in step 1. per double-click to
remove the volatile environment variable COMSPEC
again.
COMSPEC
pointing to an arbitrary Alternate Data Stream or an
arbitrary UNC path
\\‹computer›\‹share›\[‹directory›\[…\]]‹file›
is left as an exercise to the reader!
batfile
and cmdfile
associated with the extensions
.bat
and .cmd
from its default value
"%1" %*
to
C:\Windows\System32\Cmd.exe /D /C "%L" %*
:
FTYPE batfile=%COMSPEC% /D /C "%L" %* FTYPE cmdfile=%COMSPEC% /D /C "%L" %*Note: the internal
Ftype
command must be run with administrative privileges.
If you still have not abandonded the bad habit of
using the Protected Administrator account created
during Windows Setup you also need to change the
command line templates registered for the RunAs
verb:
REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
CAVEAT: thanksto the Merged View of HKEY_CLASSES_ROOT introduced with Windows 2000 this remediation but fails if the unnamed default registry entry with the default command line template is present in the corresponding registry key
HKEY_CURRENT_USER\Software\Classes\‹file type›\Shell\‹verb›\Command
of a user account!
Create the text file quirk4.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const STARTUPINFO si = {sizeof(si)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESS_INFORMATION pi;
WCHAR szCmdLine[] = L".\\quirk4.bat";
WCHAR szProcess[MAX_PATH];
DWORD dwProcess = sizeof(szProcess) / sizeof(*szProcess);
DWORD dwThread;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!CreateProcess((LPCWSTR) NULL,
szCmdLine,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
(LPWSTR) NULL,
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hConsole,
L"CreateProcess() returned error %lu\n",
dwError = GetLastError());
else
{
if (!QueryFullProcessImageName(pi.hProcess, 0, szProcess, &dwProcess))
PrintConsole(hConsole,
L"QueryFullProcessImageName() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"Child process loaded from image file \'%ls\'\n",
szProcess);
PrintConsole(hConsole,
L"Child process %lu with primary thread %lu created\n",
pi.dwProcessId, pi.dwThreadId);
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
PrintConsole(hConsole,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeThread(pi.hThread, &dwThread))
PrintConsole(hConsole,
L"GetExitCodeThread() returned error %lu\n",
dwError = GetLastError());
else
if (dwThread > 65535)
PrintConsole(hConsole,
L"Primary thread %lu of child process %lu exited with code 0x%08lX\n",
pi.dwThreadId, pi.dwProcessId, dwThread);
else
PrintConsole(hConsole,
L"Primary thread %lu of child process %lu exited with code %lu\n",
pi.dwThreadId, pi.dwProcessId, dwThread);
if (!CloseHandle(pi.hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
PrintConsole(hConsole,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeProcess(pi.hProcess, &dwProcess))
PrintConsole(hConsole,
L"GetExitCodeProcess() returned error %lu\n",
dwError = GetLastError());
else
if (dwProcess > 65535)
PrintConsole(hConsole,
L"Child process %lu exited with code 0x%08lX\n",
pi.dwProcessId, dwProcess);
else
PrintConsole(hConsole,
L"Child process %lu exited with code %lu\n",
pi.dwProcessId, dwProcess);
if (!CloseHandle(pi.hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk4.exe
from the
source file quirk4.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk4.c kernel32.lib user32.libFor 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: quirk4.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk4.c quirk4.c(58) : warning C4090: 'function' : different 'const' qualifiers Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk4.exe quirk4.obj kernel32.lib user32.lib
Create the text file quirk4.bat
with the following
content next to the console application quirk4.exe
built in step 2.:
@ECHO %CMDCMDLINE%
Execute the console application quirk4.exe
built in
step 2. a first time to prove the documentation cited above
wrong:
.\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\cmd.exe' Child process 6008 with primary thread 5092 created C:\Windows\system32\cmd.exe /c .\quirk4.bat Primary thread 5092 of child process 6008 exited with code 0 Child process 6008 exited with code 0OUCH¹: despite its lpApplicationName parameter set to
NULL
, the Win32 function
CreateProcess()
executes a batch script with only its lpCommandLine parameter
set to the (absolute or relative) path name of that batch script!
Set the environment variable COMSPEC
to the path name
of an arbitrary application, then execute the console application
quirk4.exe
a second time to show undocumented
behaviour:
SET COMSPEC=%SystemRoot%\System32\Reg.exe .\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\reg.exe' Child process 5832 with primary thread 5704 created ERROR: Invalid Argument/Option - '/c'. Type "REG /?" for usage. Primary thread 5704 of child process 5832 exited with code 1 Child process 5832 exited with code 1OUCH²: the Win32 function
CreateProcess()
evaluates the environment variable COMSPEC
and executes
an arbitrary (hostile) application!
Set the environment variable COMSPEC
to the (relative
or absolute) path name of an arbitrary
NTFS
Alternate Data Stream
that contains an arbitrary application, then execute the console
application quirk4.exe
a third time to show
undocumented behaviour:
SET COMSPEC=.\quirk4.bat:Zone.Identifier TYPE "%SystemRoot%\System32\Reg.exe" 1>"%COMSPEC%" .\quirk4.exe
Child process loaded from image file 'C:\Users\Stefan\Desktop\quirk4.bat:Zone.Identifier' Child process 6252 with primary thread 6096 created ERROR: Invalid Argument/Option - '/c'. Type "REG /?" for usage. Primary thread 6096 of child process 6252 exited with code 1 Child process 6252 exited with code 1OUCH³: the Win32 function
CreateProcess()
evaluates the environment variable COMSPEC
and executes
an arbitrary (hostile) application also from an
Alternate Data Stream!
Remove the environment variable COMSPEC
, then execute
the console application quirk4.exe
a fourth time to
show undocumented behaviour:
SET COMSPEC= .\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\cmd.exe' Child process 5436 with primary thread 5916 created C:\Windows\system32\cmd.exe /c .\quirk4.bat Primary thread 5916 of child process 5436 exited with code 0 Child process 5436 exited with code 0Oops: the Win32 function
CreateProcess()
finds the Command Processor even when
the environment variable COMSPEC
is not set!
Copy an arbitrary application (or an empty file) as
CMD.EXE
into the current directory, then execute the
console application quirk4.exe
a fifth time to show
that it doesn’t search the path, i.e. it doesn’t execute
CMD.EXE
from its application directory
or the
current directory:
COPY "%SystemRoot%\System32\Reg.exe" CMD.EXE .\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\cmd.exe' Child process 5720 with primary thread 6184 created C:\Windows\system32\cmd.exe /c .\quirk4.bat Primary thread 6184 of child process 5720 exited with code 0 Child process 5720 exited with code 0
Create the subdirectory System32\
in the current
directory, copy an arbitrary application as CMD.EXE
into it and set the environment variable SystemRoot
to
the current directory, then execute the console application
quirk4.exe
a last time to show more undocumented
behaviour:
MKDIR System32 MOVE CMD.EXE System32 SET SystemRoot=%CD% .\quirk4.exe
Child process loaded from image file 'C:\Users\Stefan\Desktop\System32\CMD.EXE' Child process 5372 with primary thread 5448 created ERROR: Invalid Argument/Option - '/c'. Type "REG /?" for usage. Primary thread 5448 of child process 5372 exited with code 1 Child process 5372 exited with code 1OUCH⁴: if the environment variable
COMSPEC
is not set the Win32 function
CreateProcess()
evaluates the environment variable SystemRoot
and
executes an arbitrary (hostile) application from
the path %SystemRoot%\System32\Cmd.exe
!
COMSPEC
or SystemRoot
pointing to an arbitrary
UNC path is left as
an exercise to the reader!
Note: an exploration of the (mis)behaviour with the
Win32 functions
CreateProcessAsUser()
,
CreateProcessWithLogonW()
and
CreateProcessWithTokenW()
is left as an exercise to the reader.
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
COMSPEC
or
SystemRoot
is a well-known weakness,
documented as
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
and
CWE-73: External Control of File Name or Path
in the
CWE™;
it allows well-known attacks like
CAPEC-13: Subverting Environment Variable Values
and
CAPEC-471: Search Order Hijacking
documented in the
CAPEC™.
If the specified process is the System process or one of the Client
Server Run-Time Subsystem (CSRSS) processes, this function fails and
the last error code is ERROR_ACCESS_DENIED
because
their access restrictions prevent user-level code from opening them.
The highlighted parts are but wrong!
The WOW64 emulator runs in user mode. It provides an interface between the 32-bit version of Ntdll.dll and the kernel of the processor, and it intercepts kernel calls. The WOW64 emulator consists of the following DLLs:These DLLs, along with the 64-bit version of Ntdll.dll, are the only 64-bit binaries that can be loaded into a 32-bit process. On Windows 10 on ARM, CHPE (Compiled Hybrid Portable Executable) binaries may also be loaded into an x86 32-bit process.
- Wow64.dll provides the core emulation infrastructure and the thunks for the Ntoskrnl.exe entry-point functions.
- Wow64Win.dll provides thunks for the Win32k.sys entry-point functions.
- (x64 only) Wow64Cpu.dll provides support for running x86 programs on x64.
- (Intel Itanium only) IA32Exec.bin contains the x86 software emulator.
- (Intel Itanium only) Wowia32x.dll provides the interface between IA32Exec.bin and WOW64.
- (ARM64 only) xtajit.dll contains the x86 software emulator.
- (ARM64 only) wowarmw.dll provides support for running ARM32 programs on ARM64.
At startup, Wow64.dll loads the x86 version of Ntdll.dll (or the CHPE version, if enabled) and runs its initialization code, which loads all necessary 32-bit DLLs. […]
NTDLL.dll
deems necessary.
Create the text file quirk8.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
int main(void)
{
return 'VOID';
}
Note: this trivial minimal program has 0 (in words:
zero) dependencies, i.e. the initialisation code of
NTDLL.dll
has no
reason to load any other
DLL
at all!
Build the 32-bit console application quirk8.exe
from
the source file quirk8.c
created in step 1.:
SET CL=/W4 /X /Zl SET LINK=/ENTRY:main /NODEFAULTLIB CL.EXE quirk8.c
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. /ENTRY:main /NODEFAULTLIB /out:quirk8.exe quirk8.objFor 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: the command lines can be copied and pasted as block into a Command Processor window.
Execute the 32-bit console application quirk8.exe
built
in step 2. under the 32-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Symbol search path is: srv* Executable search path is: ModLoad: 01230000 01232000 image01230000 ModLoad: 779c0000 77b40000 ntdll.dll ModLoad: 75610000 75720000 C:\Windows\syswow64\kernel32.dll ModLoad: 752d0000 75317000 C:\Windows\syswow64\KERNELBASE.dll ModLoad: 75540000 755e1000 C:\Windows\syswow64\ADVAPI32.DLL ModLoad: 757a0000 7584c000 C:\Windows\syswow64\msvcrt.dll ModLoad: 77420000 77439000 C:\Windows\SysWOW64\sechost.dll ModLoad: 77300000 773f0000 C:\Windows\syswow64\RPCRT4.dll ModLoad: 750e0000 75140000 C:\Windows\syswow64\SspiCli.dll ModLoad: 750d0000 750dc000 C:\Windows\syswow64\CRYPTBASE.dllOOPS¹: 8 (in words: eight) not explicitly referenced and thus neither necessary nor required DLLs are loaded in
Caveat: on 64-bit systems, a 32-bit debugger
doesn’t receive LOAD_DLL_DEBUG_EVENT
and
UNLOAD_DLL_DEBUG_EVENT
events for 64-bit
DLLs.
Execute the 32-bit console application quirk8.exe
built
in step 2. under the 64-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Symbol search path is: srv* Executable search path is: ModLoad: 00000000`12340000 00000000`12342000 image00000000`12340000 ModLoad: 00000000`77800000 00000000`7799f000 ntdll.dll ModLoad: 00000000`779c0000 00000000`77b40000 ntdll32.dll ModLoad: 00000000`75040000 00000000`7507f000 C:\Windows\SYSTEM32\wow64.dll ModLoad: 00000000`74fe0000 00000000`7503c000 C:\Windows\SYSTEM32\wow64win.dll ModLoad: 00000000`74fd0000 00000000`74fd8000 C:\Windows\SYSTEM32\wow64cpu.dll ModLoad: 00000000`775e0000 00000000`776ff000 WOW64_IMAGE_SECTION ModLoad: 00000000`75610000 00000000`75720000 WOW64_IMAGE_SECTION ModLoad: 00000000`775e0000 00000000`776ff000 NOT_AN_IMAGE ModLoad: 00000000`77700000 00000000`777fb000 NOT_AN_IMAGE ModLoad: 00000000`75610000 00000000`75720000 C:\Windows\syswow64\kernel32.dll ModLoad: 00000000`752d0000 00000000`75317000 C:\Windows\syswow64\KERNELBASE.dll ModLoad: 00000000`75540000 00000000`755e1000 C:\Windows\syswow64\ADVAPI32.DLL ModLoad: 00000000`757a0000 00000000`7584c000 C:\Windows\syswow64\msvcrt.dll ModLoad: 00000000`77420000 00000000`77439000 C:\Windows\SysWOW64\sechost.dll ModLoad: 00000000`77300000 00000000`773f0000 C:\Windows\syswow64\RPCRT4.dll ModLoad: 00000000`750e0000 00000000`75140000 C:\Windows\syswow64\SspiCli.dll ModLoad: 00000000`750d0000 00000000`750dc000 C:\Windows\syswow64\CRYPTBASE.dllOOPS²: as before!
Build the 64-bit console application quirk8.exe
from
the source file quirk8.c
created in step 1.:
SET CL=/W4 /X /Zl SET LINK=/ENTRY:main /NODEFAULTLIB CL.EXE quirk8.c
Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 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. /ENTRY:main /NODEFAULTLIB /out:quirk8.exe quirk8.obj
Execute the 64-bit console application quirk8.exe
built
in step 5. under the 64-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Symbol search path is: srv* Executable search path is: ModLoad: 00000000`12340000 00000000`12342000 image00000000`12340000 ModLoad: 00000000`77800000 00000000`7799f000 ntdll.dll ModLoad: 00000000`775e0000 00000000`776ff000 C:\Windows\system32\kernel32.dll ModLoad: 000007fe`fd510000 000007fe`fd577000 C:\Windows\system32\KERNELBASE.dll ModLoad: 000007fe`fd980000 000007fe`fda5b000 C:\Windows\system32\ADVAPI32.dll ModLoad: 000007fe`fe1c0000 000007fe`fe25f000 C:\Windows\system32\msvcrt.dll ModLoad: 000007fe`fe260000 000007fe`fe27f000 C:\Windows\SYSTEM32\sechost.dll ModLoad: 000007fe`ff250000 000007fe`ff37c000 C:\Windows\system32\RPCRT4.dllOOPS³: 6 (in words: six) not explicitly referenced and thus neither necessary nor required DLLs are loaded in
Finally (try to) execute the 64-bit console application
quirk8.exe
built in step 5. under the 32-bit
debugger CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Cannot execute '.\quirk8.exe', Win32 error 0n50 "The request is not supported." Debuggee initialization failed, Win32 error 0n50 "The request is not supported."
GetOpenClipboardWindow()
,
GetWindow()
and
GetWindowModuleFileName()
are documented in the
MSDN as
follows:
Retrieves the handle to the window that currently has the clipboard open.[…]HWND GetOpenClipboardWindow();
If the function succeeds, the return value is the handle to the window that has the clipboard open. If no window has the clipboard open, the return value is NULL. To get extended error information, call GetLastError.
Retrieves a handle to a window that has the specified relationship (Z-Order or owner) to the specified window.HWND GetWindow( HWND hWnd, UINT uCmd );
[…]
Value Meaning … … GW_ENABLEDPOPUP
6The retrieved handle identifies the enabled popup window owned by the specified window (the search uses the first such window found using GW_HWNDNEXT); otherwise, if there are no enabled popup windows, the retrieved handle is that of the specified window. […]
If the function succeeds, the return value is a window handle. If no window exists with the specified relationship to the specified window, the return value is NULL. To get extended error information, call GetLastError.
Retrieves the full path and file name of the module associated with the specified window handle.Note: the last documentation fails to specify what[…]UINT GetWindowModuleFileName( HWND hwnd, LPTSTR pszFileName, UINT cchFileNameMax );
The return value is the total number of characters copied into the buffer.
associated modulemeans as well as error conditions and restrictions! 228469
Create the text file quirk14.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBuffer[MAX_PATH];
DWORD dwBuffer;
DWORD dwError = ERROR_SUCCESS;
HWND hWindow = HWND_DESKTOP;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
SetLastError(123456789);
hWindow = GetActiveWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetActiveWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetConsoleWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetConsoleWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
SetLastError(123456789);
hWindow = GetDesktopWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetDesktopWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetForegroundWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetForegroundWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
SetLastError(123456789);
hWindow = GetOpenClipboardWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetOpenClipboardWindow() returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetShellWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetShellWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetTopWindow(HWND_DESKTOP);
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetTopWindow() returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
SetLastError(123456789);
hWindow = GetWindow(hWindow, GW_ENABLEDPOPUP);
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetWindow() returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
dwBuffer = GetWindowModuleFileName(hWindow = HWND_BOTTOM,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
dwBuffer = GetWindowModuleFileName(hWindow = HWND_TOP,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
dwBuffer = GetWindowModuleFileName(hWindow = HWND_TOPMOST,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
dwBuffer = GetWindowModuleFileName(hWindow = HWND_NOTOPMOST,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk14.exe
from the
source file quirk14.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk14.c kernel32.lib user32.libFor 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: quirk14.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk14.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk14.exe quirk14.obj kernel32.lib user32.lib
Execute the console application quirk14.exe
built in
step 2. to demonstrate the inconsistent (mis)behaviour:
.\quirk14.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetWindowModuleFileName(0x00000000, …) returned error 1400 GetWindow() returned error 1400 GetActiveWindow() returned NULL GetWindowModuleFileName(0x0025021C, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters GetWindowModuleFileName(0x00010010, …) returned error 123456789 GetWindowModuleFileName(0x0025021C, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters GetOpenClipboardWindow() returned error 123456789 GetWindowModuleFileName(0x00020104, …) returned error 123456789 GetWindowModuleFileName(0x035D0EA0, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters GetWindow() returned error 123456789 GetWindowModuleFileName(0x00000001, …) returned error 1400 GetWindowModuleFileName(0x00000000, …) returned error 1400 GetWindowModuleFileName(0xFFFFFFFF, …) returned error 1400 GetWindowModuleFileName(0xFFFFFFFE, …) returned error 1400 0x578 (WIN32: 1400 ERROR_INVALID_WINDOW_HANDLE) -- 1400 (1400) Error message text: Invalid window handle. CertUtil: -error command completed successfully.OUCH¹: the Win32 function
GetWindow()
does not support the pseudo handle
HWND_DESKTOP
alias NULL
, but returns
NULL
with Win32 error code 1400 alias
ERROR_INVALID_WINDOW_HANDLE
set!
OUCH²: it does not return
the handle of the specified window if this does not own an enabled
popup window, but NULL
and fails to set the
Win32 error code!
OUCH³: it returns NULL
but fails
to set the Win32 error code if no window with the
specified relationship exists!
Note: the Win32 function
GetWindowModuleFileName()
returns 0 and does not modify the buffer in case of
failure!
OUCH⁴: it returns the path name of the calling console application although this did not create the window!
OUCH⁵: it returns 0 but fails to set the Win32 error code if the window was created in another process, except for the console window!
OUCH⁶: it does not support
the pseudo handles
HWND_BOTTOM
,
HWND_TOP
alias HWND_DESKTOP
,
HWND_TOPMOST
and
HWND_NOTOPMOST
,
but returns 0 with Win32 error code 1400 alias
ERROR_INVALID_WINDOW_HANDLE
set!
OUCH⁷: the Win32 function
GetOpenClipboardWindow()
returns NULL
but fails to set the Win32
error if the clipboard is not opened by a window!
An access control list (ACL) is a list of access control entries (ACE). Each ACE in an ACL identifies a trustee and specifies the access rights allowed, denied, or audited for that trustee. The security descriptor for a securable object can contain two types of ACLs: a DACL and a SACL.Note: no document denotes different access rights for deletion of directories than for deletion of files, which both are filesystem objects and children of a parent filesystem object, i.e. deletion should be governed byA discretionary access control list (DACL) identifies the trustees that are allowed or denied access to a securable object. When a process tries to access a securable object, the system checks the ACEs in the object's DACL to determine whether to grant access to it. If the object does not have a DACL, the system grants full access to everyone. If the object's DACL has no ACEs, the system denies all attempts to access the object because the DACL does not allow any access rights. The system checks the ACEs in sequence until it finds one or more ACEs that allow all the requested access rights, or until any of the requested access rights are denied. […]
FILE_DELETE_CHILD
of the parent (too)!
The TechNet article How Permissions Work specifies:
Folder permissions include Full Control, Modify, Read & Execute, List Folder Contents, Read, and Write. Each of these permissions consists of a logical group of special permissions that are listed and defined in the following table.The MSDN article File Security and Access Rights but contradicts:Permissions for Files and Folders
Permission Description … … Delete Subfolders and Files Allows or denies deleting subfolders and files, even if the Delete permission has not been granted on the subfolder or file. (Applies to folders.) Delete Allows or denies deleting the file or folder. If you do not have Delete permission on a file or folder, you can still delete it if you have been granted Delete Subfolders and Files on the parent folder. […]
You should also be aware of the following:
Groups or users that are granted Full Control on a folder can delete any files in that folder, regardless of the permissions protecting the file.
The valid access rights for files and directories include the DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE standard access rights.The MSDN articles SACL Access Right and Requesting Access Rights to an Object specify:[…]
By default, authorization for access to a file or directory is controlled strictly by the ACLs in the security descriptor associated with that file or directory. In particular, the security descriptor of a parent directory is not used to control access to any child file or directory.
The ACCESS_SYSTEM_SECURITY access right is not valid in a DACL because DACLs do not control access to a SACL.
Contrary to the last two (highlighted) statements, the documentation as well as the synopsis of Windows’Note
The MAXIMUM_ALLOWED constant cannot be used in an ACE.
Icacls
command line program but state:
Displays or modifies discretionary access control lists (DACLs) on specified files, and applies stored DACLs to files in specified directories.Oops: the parameter[…]
[…]icacls ‹FileName› [/grant[:r] ‹Sid›:‹Perm›[…]] [/deny ‹Sid›:‹Perm›[…]] [/remove[:g|:d]] ‹Sid›[…]] [/t] [/c] [/l] [/q] [/setintegritylevel ‹Level›:‹Policy›[…]]
Perm is a permission mask that can be specified in one of the following forms:
A sequence of simple rights:
[…]
A comma-separated list in parenthesis of specific rights:
D (delete)
RC (read control)
WDAC (write DAC)
WO (write owner)
S (synchronize)
AS (access system security)
MA (maximum allowed)
[…]
RD (read data/list directory)
WD (write data/add file)
AD (append data/add subdirectory)
[…]
DC (delete child)
/Inheritance:{E|D|R}
is undocumented!
Command-Line Reference
Cmd.exe
, then execute the
following command lines to prove the documentation cited above
wrong, and also show some undocumented (mis)behaviour:
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> CHDIR /D "%TMP%" COPY NUL: Quirk16f.tmp ECHO Step 1: remove all inherited access permissions from file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Inheritance:R /Q CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp /Q ECHO Step 2: (attempt to) add inheritable access permissions to file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Grant *S-1-3-3:(CI)(D) /Grant *S-1-3-2:(OI)(S) /Grant *S-1-3-1:(CI)(IO)(RC) /Grant *S-1-3-0:(OI)(IO)(WDAC) /Q CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp /Q ECHO Step 3: add access permissions to file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Deny *S-1-3-4:(AS) /Grant *S-1-3-4:(MA) /Grant *S-1-3-3:(D) /Grant *S-1-3-2:(RC) /Grant *S-1-3-1:(S) /Grant *S-1-3-0:(WDAC) /Q CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp /Q ECHO Step 4: remove access permissions from file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Q /Remove:g "%USERNAME%" /Remove:g None CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp /Q ECHO Step 5: delete file 'Quirk16f.tmp' ERASE Quirk16f.tmpNote: the command lines can be copied and pasted as block into a Command Processor window.
1 file(s) copied. Step 1: remove all inherited access permissions from file 'Quirk16f.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI" Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files Step 2: (attempt to) add inheritable access permissions to file 'Quirk16f.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI" Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files Step 3: add access permissions to file 'Quirk16f.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI(D;;;;;OW)(A;;WD;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;RC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;0x110000;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;;;;OW)" Quirk16f.tmp OWNER RIGHTS:(DENY)(S) AMNESIAC\Stefan:(WDAC) AMNESIAC\None:(S) AMNESIAC\Stefan:(Rc) AMNESIAC\None:(D) OWNER RIGHTS: Successfully processed 1 files; Failed processing 0 files Step 4: remove access permissions from file 'Quirk16f.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp Access denied Quirk16f.tmp: Access denied Successfully processed 0 files; Failed processing 1 files Step 5: delete file 'Quirk16f.tmp'Note:
ICACLs.exe
does
not add inheritable
ACEs to files, but
discards them silently, reporting success.
Ouch¹: ICACLs.exe
converts its access permissions
AS
alias
ACCESS_SYSTEM_SECURITY
and
MA
alias
MAXIMUM_ALLOWED
to 0 alias
NO_ACCESS
!
Ouch²: ICACLs.exe
converts its access permission D
into
the compound access mask
SD
alias
DELETE
plus
SYNCHRONIZE
, and displays this
compound access mask 0x110000
as its
access permission D
too!
Note: ICACLs.exe
converts the
well-known SIDs
S-1-3-0
alias
CREATOR OWNER
,
S-1-3-1
alias
CREATOR GROUP
,
S-1-3-2
alias
CREATOR OWNER SERVER
, and
S-1-3-3
alias
CREATOR GROUP SERVER
into the
object’s effective user and (primary) group
SIDs.
Ouch³: ICACLs.exe
displays the access mask 0 alias
NO_ACCESS
of the
ACE
(D;;;;;OW)
as (S)
alias
SYNCHRONIZE
!
Note: both ICACLs.exe
and
CACLs.exe
fail (expected)
when the ACE
(D;;;;;OW)
with access mask 0 alias
NO_ACCESS
is present in the
DACL
– it overrides the implicit
RC
alias
READ_CONTROL
and
WD
alias
WRITE_DAC
access rights granted to
the object’s owner and has the same effect as the
ACE
(A;;;;;OW)
.
The TechNet article Security Identifiers Technical Overview specifies:
The following table lists the universal well-known SIDs.AD DS: Owner RightsUniversal well-known SIDs
Value Universal Well-Known SID Identifies … … … S-1-3-4 Owner Rights A group that represents the current owner of the object. When an ACE that carries this SID is applied to an object, the system ignores the implicit READ_CONTROL and WRITE_DAC permissions for the object owner.
MKDIR Quirk16d.tmp ECHO Step A: remove all inherited access permissions from directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Inheritance:R /Q CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp /Q ECHO Step B: add (inheritable) access permissions to directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Deny *S-1-3-4:(AS,MA) /Grant *S-1-3-3:(CI)(RC,WDAC) /Grant *S-1-3-2:(OI)(RD,WD) /Grant *S-1-3-1:(CI)(IO)(AS) /Grant *S-1-3-0:(OI)(IO)(MA) /Q CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp /Q ECHO Step C: (attempt to) delete directory 'Quirk16d.tmp' RMDIR Quirk16d.tmp ECHO Step D: remove all inheritable access permissions from directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Q /Remove:g *S-1-3-3 /Remove:g *S-1-3-2 /Remove:g *S-1-3-1 /Remove:g *S-1-3-0 CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp /Q ECHO Step E: create file 'Quirk16d.tmp\Quirk16f.tmp' COPY NUL: Quirk16d.tmp\Quirk16f.tmp ECHO Step F: display access permissions of file 'Quirk16d.tmp\Quirk16f.tmp' ICACLS.EXE Quirk16d.tmp\Quirk16f.tmp /Q CACLS.EXE Quirk16d.tmp\Quirk16f.tmp /S ECHO Step G: (attempt to) delete file 'Quirk16d.tmp\Quirk16f.tmp' ERASE Quirk16d.tmp\Quirk16f.tmp ECHO Step H: add access permissions to directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Grant *S-1-3-1:(AD) /Grant *S-1-3-0:(S) /Q CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp /Q CACLS.EXE Quirk16d.tmp\Quirk16f.tmp /S ECHO Step I: delete file 'Quirk16d.tmp\Quirk16f.tmp' ERASE Quirk16d.tmp\Quirk16f.tmp ECHO Step J: remove access permissions from directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Q /Remove:g "%USERNAME%" CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp /Q ECHO Step K: create subdirectory 'Quirk16d.tmp\Quirk16d.tmp' MKDIR Quirk16d.tmp\Quirk16d.tmp ECHO Step L: delete subdirectory 'Quirk16d.tmp\Quirk16d.tmp' RMDIR Quirk16d.tmp\Quirk16d.tmp ECHO Step M: (attempt to) delete directory 'Quirk16d.tmp' RMDIR Quirk16d.tmp ECHO Step N: modify access permissions of directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Grant *S-1-3-0:(S) /Q /Remove:g None CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp /Q ECHO Step O: delete directory 'Quirk16d.tmp' RMDIR Quirk16d.tmpNote: the command lines can be copied and pasted as block into a Command Processor window.
Step A: remove all inherited access permissions from directory 'Quirk16d.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI" Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files Step B: add (inheritable) access permissions to directory 'Quirk16d.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;OIIO;0x2000000;;;CO)(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;OIIO;CCDC;;;S-1-3-2)(A;CIIO;0x1000000;;;CG)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)(A;CIIO;RCWD;;;S-1-3-3)" Quirk16d.tmp OWNER RIGHTS:(DENY)(S) CREATOR OWNER:(OI)(IO)(MA) AMNESIAC\Stefan:(RD,WD) CREATOR OWNER SERVER:(OI)(IO)(RD,WD) CREATOR GROUP:(CI)(IO)(AS) AMNESIAC\None:(Rc,WDAC) CREATOR GROUP SERVER:(CI)(IO)(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files Step C: (attempt to) delete directory 'Quirk16d.tmp' Access denied Step D: remove all inheritable access permissions from directory 'Quirk16d.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)" Quirk16d.tmp AMNESIAC\Stefan:(RD,WD,AD) AMNESIAC\None:(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files Step E: create file 'Quirk16d.tmp\Quirk16f.tmp' 1 file(s) copied. Step F: display access permissions of file 'Quirk16d.tmp\Quirk16f.tmp' Quirk16d.tmp\Quirk16f.tmp AMNESIAC\Stefan:(F) NT AUTHORITY\SYSTEM:(F) The mapping between account names and security IDs was done. (RX) Successfully processed 1 files; Failed processing 0 files Access denied Step G: (attempt to) delete file 'Quirk16d.tmp\Quirk16f.tmp' Could Not Find C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp\Quirk16f.tmp Step H: add access permissions to directory 'Quirk16d.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;LC;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)" Quirk16d.tmp OWNER RIGHTS:(DENY)(S) AMNESIAC\Stefan:(AD) AMNESIAC\None:(S) AMNESIAC\Stefan:(RD,WD) AMNESIAC\None:(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp\Quirk16f.tmp "D:AI(A;;FA;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;FA;;;SY)(A;;0x1200a9;;;S-1-5-5-0-231840)" Step I: delete file 'Quirk16d.tmp\Quirk16f.tmp' Step J: remove access permissions from directory 'Quirk16d.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;;LC;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)" Quirk16d.tmp OWNER RIGHTS:(DENY)(S) AMNESIAC\None:(S) AMNESIAC\None:(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files Step K: create subdirectory 'Quirk16d.tmp\Quirk16d.tmp' Step L: delete subdirectory 'Quirk16d.tmp\Quirk16d.tmp' Step M: (attempt to) delete directory 'Quirk16d.tmp' Access denied Step N: modify access permissions of directory 'Quirk16d.tmp' Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp Access denied Quirk16d.tmp: Access denied Successfully processed 0 files; Failed processing 1 files Step O: delete directory 'Quirk16d.tmp'Ouch⁴:
ICACLs.exe
adds
inheritable ACEs
with the invalid access masks
AS
alias
ACCESS_SYSTEM_SECURITY
and
MA
alias
MAXIMUM_ALLOWED
to
DACLs!
Note: without an inheritable ACE from its parent object the default security descriptor from the process’s access token is applied to a new object.
Ouch⁵: both
CACLs.exe
and the internal
Del
alias
Erase
command of the Command Processor
Cmd.exe
fail without
SYNCHRONIZE
access permission to
the parent directory of the filesystem object to access!
Note: both ICACLs.exe
and
CACLs.exe
fail (expected)
when the ACE
(D;;;;;OW)
with access mask 0 alias
NO_ACCESS
is present in the
DACL
– it overrides the implicit
RC
alias
READ_CONTROL
and
WD
alias
WRITE_DAC
access rights granted to
the object’s owner and has the same effect as the
ACE
(A;;;;;OW)
.
Ouch⁶: the internal
Rd
alias
Rmdir
command of the Command Processor
succeeds despite the missing SD
alias DELETE
access permission!
DELETE
access permission as well as
to deny (un)intentionally any access to (filesystem) objects via the
ACCESS_SYSTEM_SECURITY
and
MAXIMUM_ALLOWED
access permissions!
They replied with the following statements:
Thank you for your submission. We determined your finding does not meet our bar for immediate servicing. For more information, please see the Microsoft Security Servicing Criteria for Windows (https://aka.ms/windowscriteria).However, we’ve marked your finding for future review as an opportunity to improve our products. I do not have a timeline for this review and will not provide updates moving forward. As no further action is required at this time, I am closing this case. You will not receive further correspondence regarding this submission.
CreateDirectory()
and
CreateFile()
are documented in the
MSDN as
follows:
Creates a new directory. If the underlying file system supports security on files and directories, the function applies a specified security descriptor to the new directory.[…]
[…]BOOL CreateDirectory( LPCTSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
lpSecurityAttributes
A pointer to a SECURITY_ATTRIBUTES structure. The lpSecurityDescriptor member of the structure specifies a security descriptor for the new directory. […]
Creates or opens a file or I/O device. […]The[…]BOOL CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );
lpSecurityAttributes
A pointer to a SECURITY_ATTRIBUTES structure that contains two separate but related data members: an optional security descriptor, […]
The lpSecurityDescriptor member of the structure specifies a SECURITY_DESCRIPTOR for a file or device. […]
SECURITY_ATTRIBUTES
and
SECURITY_DESCRIPTOR
structures are documented as follows:
[…]typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
lpSecurityAttributes
A pointer to a SECURITY_DESCRIPTOR structure that controls access to the object. […]
A security descriptor includes information that specifies the following components of an object's security:File Management Functions Directory Management Functions The Win32 functions
- An owner security identifier (SID)
- A primary group SID
- A discretionary access control list (DACL)
- A system access control list (SACL)
- Qualifiers for the preceding items
DeleteFile()
,
DeleteFileTransacted()
,
MoveFileEx()
,
MoveFileWithProgress()
,
RemoveDirectory()
and
ReplaceFile()
are documented in the
MSDN as
follows:
Deletes an existing file.[…]
[…]BOOL DeleteFile( LPCTSTR lpFileName );
lpFileName
The name of the file to be deleted.
[…]
- To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory.
- To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory.
Moves an existing file or directory, including its children, with various move options.Oops: the highlighted sentences above and below but contradict the linked MSDN article File Security and Access Rights by 5÷1![…]
[…]BOOL MoveFileEx( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags );
lpExistingFileName
The current name of the file or directory on the local computer.
[…]
lpNewFileName
The new name of the file or directory on the local computer.
[…]
- To delete or rename a file, you must have either delete permission on the file or delete child permission in the parent directory. If you set up a directory with all access except delete and delete child and the ACLs of new files are inherited, then you should be able to create a file without being able to delete it. However, you can then create a file, and get all the access you request on the handle that is returned to you at the time that you create the file. If you request delete permission at the time you create the file, you can delete or rename the file with that handle but not with any other handle. For more information, see File Security and Access Rights.
Moves a file or directory, including its children. You can provide a callback function that receives progress notifications.[…]
[…]BOOL MoveFileWithProgress( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, DWORD dwFlags );
lpExistingFileName
The current name of the file or directory on the local computer.
[…]
lpNewFileName
The new name of the file or directory on the local computer.
[…]
- To delete or rename a file, you must have either delete permission on the file or delete child permission in the parent directory. If you set up a directory with all access except delete and delete child and the ACLs of new files are inherited, then you should be able to create a file without being able to delete it. However, you can then create a file, and you will get all the access you request on the handle returned to you at the time you create the file. If you requested delete permission at the time you created the file, you could delete or rename the file with that handle but not with any other.
Deletes an existing empty directory.[…]
[…]BOOL RemoveDirectory( LPCTSTR lpPathName );
lpPathName
The path of the directory to be removed. This path must specify an empty directory, and the calling process must have delete access to the directory.
RemoveDirectory()
but fails to delete empty directories created with the same access
rights as files!
- To delete or rename a file, you must have either delete permission on the file or delete child permission in the parent directory. If you set up a directory with all access except delete and delete child and the DACLs of new files are inherited, then you should be able to create a file without being able to delete it. However, you can then create a file, and you will get all the access you request on the handle returned to you at the time you create the file. If you requested delete permission at the time you created the file, you could delete or rename the file with that handle but not with any other.
Create the text file quirk17.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <sddl.h>
#include <aclapi.h>
#ifndef _WIN64
#pragma intrinsic(memcmp)
#else
#pragma function(memcmp)
int memcmp(char const *left, char const *right, size_t count)
{
size_t index;
int delta;
for (index = 0; index < count; index++)
if (delta = left[index] - right[index])
return delta;
return 0;
}
#endif
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace;
} acl = {{ACL_REVISION, 0, sizeof(acl), 1, 0},
#if 0 // (A;NP;FA;;;AU)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
FILE_ALL_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID}}},
#else // (A;NP;SD;;;OW)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
DELETE,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
#endif
dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
#ifndef QUIRKS // (A;NP;0x1f0000;;;OW)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
STANDARD_RIGHTS_ALL,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
#elif QUIRKS == 0 // (A;NP;FA;;;AU)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
FILE_ALL_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID}}},
#elif QUIRKS == 1 // (A;NP;0x1f0000;;;S-1-5-15)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
STANDARD_RIGHTS_ALL,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_THIS_ORGANIZATION_RID}}},
#elif QUIRKS == 2 // (A;NP;FA;;;PS)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
FILE_ALL_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_PRINCIPAL_SELF_RID}}},
#elif QUIRKS == 3 // (A;NP;0x1f0000;;;S-1-5-1000)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
STANDARD_RIGHTS_ALL,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_OTHER_ORGANIZATION_RID}}},
#elif QUIRKS == 4 // (A;NP;0x3000000;;;IU)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_INTERACTIVE_RID}}},
#else // NOTE: construct an INVALID DACL!
{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}},
#endif
sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
// (ML;;NRNWNX;;;ME)
{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
#ifdef QUIRKS
SE_DACL_PRESENT | SE_DACL_PROTECTED,
#else // BUG: CreateFile*() and CreateDirectory*() fail with ERROR_PRIVILEGE_NOT_HELD when a "mandatory label" is present!
SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_SACL_PRESENT | SE_SACL_PROTECTED,
#endif
(SID *) NULL,
(SID *) NULL,
#ifdef QUIRKS
(ACL *) NULL,
#else
&sacl,
#endif
&dacl};
const SECURITY_ATTRIBUTES sa = {sizeof(sa),
&sd,
FALSE};
const WCHAR szName[] = L"Quirk17.tmp";
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SECURITY_DESCRIPTOR *lpSD;
ACL *lpDACL;
LPWSTR lpSDDL;
DWORD dwError;
DWORD dwFile;
HANDLE hFile;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(&sd,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
(LPDWORD) NULL))
PrintConsole(hConsole,
L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"Using security descriptor \'%ls\' to create file and directory \'%ls\'\n",
lpSDDL, szName);
if (LocalFree(lpSDDL) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
hFile = CreateFile(szName,
FILE_WRITE_DATA | READ_CONTROL,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
CREATE_NEW,
#if 0
FILE_FLAG_DELETE_ON_CLOSE,
#else
FILE_ATTRIBUTE_NORMAL,
#endif
(HANDLE) NULL);
if (hFile == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateFile() returned error %lu\n",
dwError = GetLastError());
else
{
dwError = GetSecurityInfo(hFile,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
&lpDACL,
(ACL **) NULL,
&lpSD);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"GetSecurityInfo() returned error %lu\n",
dwError);
else
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
(LPDWORD) NULL))
PrintConsole(hConsole,
L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"File \'%ls\' created with security descriptor \'%ls\'\n",
szName, lpSDDL);
if (LocalFree(lpSDDL) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (memcmp(lpDACL, &dacl, sizeof(dacl)) != 0)
PrintConsole(hConsole,
L"DACL of file differs from original DACL!\n");
if (LocalFree(lpSD) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (!WriteFile(hFile,
L"\xFEFF", // UTF-16LE byte order mark
sizeof(L'\xFEFF'),
&dwFile,
(LPOVERLAPPED) NULL))
PrintConsole(hConsole,
L"WriteFile() returned error %lu\n",
dwError = GetLastError());
else
if (dwFile != sizeof(L'\xFEFF'))
PrintConsole(hConsole,
L"WriteFile() failed, %lu of %lu bytes written\n",
dwFile, sizeof(L'\xFEFF'));
if (!CloseHandle(hFile))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!DeleteFile(szName))
{
PrintConsole(hConsole,
L"DeleteFile() returned error %lu\n",
dwError = GetLastError());
if (dwError == ERROR_ACCESS_DENIED)
{
dwError = SetNamedSecurityInfo(szName,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
(SID *) NULL,
(SID *) NULL,
&acl,
(ACL *) NULL);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"SetNamedSecurityInfo() returned error %lu\n",
dwError);
else
if (!DeleteFile(szName))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu\n",
dwError = GetLastError());
dwError = ERROR_ACCESS_DENIED;
}
}
}
if (!CreateDirectory(szName,
&sa))
PrintConsole(hConsole,
L"CreateDirectory() returned error %lu\n",
dwError = GetLastError());
else
{
dwError = GetNamedSecurityInfo(szName,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
&lpDACL,
(ACL **) NULL,
&lpSD);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"GetNamedSecurityInfo() returned error %lu\n",
dwError);
else
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
(LPDWORD) NULL))
PrintConsole(hConsole,
L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"Directory \'%ls\' created with security descriptor \'%ls\'\n",
szName, lpSDDL);
if (LocalFree(lpSDDL) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (memcmp(lpDACL, &dacl, sizeof(dacl)) != 0)
PrintConsole(hConsole,
L"DACL of directory differs from original DACL!\n");
if (LocalFree(lpSD) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (!RemoveDirectory(szName))
{
PrintConsole(hConsole,
L"RemoveDirectory() returned error %lu\n",
dwError = GetLastError());
if (dwError == ERROR_ACCESS_DENIED)
{
dwError = SetNamedSecurityInfo(szName,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
(SID *) NULL,
(SID *) NULL,
&acl,
(ACL *) NULL);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"SetNamedSecurityInfo() returned error %lu\n",
dwError);
else
if (!RemoveDirectory(szName))
PrintConsole(hConsole,
L"RemoveDirectory() returned error %lu\n",
dwError = GetLastError());
dwError = ERROR_ACCESS_DENIED;
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk17.exe
from the
source file quirk17.c
created in step 1., with the
preprocessor macro QUIRKS
defined as 0 or 1:
SET CL=/GAFy /Oisy /W4 /wd4090 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=0 quirk17.c advapi32.lib kernel32.lib user32.lib/D (Preprocessor Definitions) For 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: quirk17.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 2. to verify its proper function:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Using security descriptor 'D:P(A;NP;FA;;;AU)' to create file and directory 'Quirk17.tmp' File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;AU)' Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;AU)' 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.A file as well as a directory created (for example) with only the DACL
D:P
(A;NP;FA;;;AU)
or
D:P
(A;NP;0x1f0000;;;S-1-5-15)
can be deleted.
Build the console application quirk17.exe
a second time
from the source file quirk17.c
created in step 1.,
now with the preprocessor macro QUIRKS
defined as 2 or
3:
CL.EXE /DQUIRKS=2 quirk17.c advapi32.lib kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 4. to show the misbehaviour:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Using security descriptor 'D:P(A;NP;FA;;;PS)' to create file and directory 'Quirk17.tmp'
File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;PS)'
Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;PS)'
RemoveDirectory() returned error 5
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
Error message text: Access denied.
CertUtil: -error command completed successfully.
OUCH: while a file created (for example) with only
the
DACL
D:P
(A;NP;FA;;;PS)
or
D:P
(A;NP;0x1f0000;;;S-1-5-1000)
can be deleted, a directory created with only this
DACL
can’t be deleted!
Build the console application quirk17.exe
a third time
from the source file quirk17.c
created in step 1.,
now with the preprocessor macro QUIRKS
defined as 4:
CL.EXE /DQUIRKS=4 quirk17.c advapi32.lib kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 6. to show a second misbehaviour:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Using security descriptor 'D:P(A;NP;0x3000000;;;IU)' to create file and directory 'Quirk17.tmp' File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;;;;IU)' DACL of file differs from original DACL! Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;;;;IU)' DACL of directory differs from original DACL! RemoveDirectory() returned error 5 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH: the Win32 functions
ConvertSecurityDescriptorToStringSecurityDescriptor()
,
CreateDirectory()
and
CreateFile()
fail to detect the (intentionally) invalid
DACL
D:P
(A;NP;0x3000000;;;IU)
– the latter functions apply a
DACL
D:P
(A;NP;;;;IU)
instead, granting only implicit
(A;NP;WDRC;;;OW)
access for the object’s owner!
Build the console application quirk17.exe
a fourth time
from the source file quirk17.c
created in step 1.,
now with the preprocessor macro QUIRKS
defined as 5:
CL.EXE /DQUIRKS=5 quirk17.c advapi32.lib kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 8. to show a third misbehaviour:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
ConvertSecurityDescriptorToStringSecurityDescriptor() returned error 1336 File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P' DACL of file differs from original DACL! Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P' DACL of directory differs from original DACL! RemoveDirectory() returned error 5 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH: while the Win32 function
ConvertSecurityDescriptorToStringSecurityDescriptor()
now properly detects the (intentionally) invalid
DACL
D:P
(ML;;NXNRNW;;;ME)
and returns the Win32 error code 1336 alias
ERROR_INVALID_ACL
,
both
CreateDirectory()
and
CreateFile()
fail to detect it and apply the empty
DACL
D:P
instead,
again granting only implicit
(A;NP;WDRC;;;OW)
access for the object’s owner!
GetClassNameA()
,
GetWindowTextA()
,
GetWindowTextLengthA()
and
RealGetWindowClassA()
are documented in the
MSDN as
follows:
Retrieves the name of the class to which the specified window belongs.[…]int GetClassNameA( HWND hWnd, LPSTR lpClassName, int nMaxCount );
hWnd
A handle to the window and, indirectly, the class to which the window belongs.
lpClassName
The class name string.
nMaxCount
The length of the lpClassName buffer, in characters. The buffer must be large enough to include the terminating null character; otherwise, the class name string is truncated to
nMaxCount-1
characters.[…]
If the function succeeds, the return value is the number of characters copied to the buffer, not including the terminating null character.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Copies the text of the specified window's title bar (if it has one) into a buffer. […][…]int GetWindowTextA( HWND hWnd, LPSTR lpString, int nMaxCount );
hWnd
A handle to the window or control containing the text.
lpString
The buffer that will receive the text. If the string is as long or longer than the buffer, the string is truncated and terminated with a null character.
nMaxCount
The maximum number of characters to copy to the buffer, including the null character. If the text exceeds this limit, it is truncated.
[…]
If the function succeeds, the return value is the length, in characters, of the copied string, not including the terminating null character. If the window has no title bar or text, if the title bar is empty, or if the window or control handle is invalid, the return value is zero. To get extended error information, call GetLastError.
Retrieves the length, in characters, of the specified window's title bar text (if the window has a title bar). […][…]int GetWindowTextLengthA( HWND hWnd );
hWnd
A handle to the window or control.
[…]
If the function succeeds, the return value is the length, in characters, of the text. […]
If the window has no text, the return value is zero.
Retrieves a string that specifies the window type.[…]UINT RealGetWindowClassW( HWND hwnd, LPWSTR ptszClassName, UINT cchClassNameMax );
hwnd
A handle to the window whose type will be retrieved.
ptszClassName
A pointer to a string that receives the window type.
cchClassNameMax
The length, in characters, of the buffer pointed to by the pszType parameter.
[…]
If the function succeeds, the return value is the number of characters copied to the specified buffer.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
GetClassNameA()
function with
code page
65001 alias CP_UTF8
.
Create the text file quirk41.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define CLASS L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
#define TITLE L"€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›"
LRESULT WINAPI WindowProc(HWND hWindow, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
if (uMessage == WM_DESTROY)
PostQuitMessage(0);
return DefWindowProcA(hWindow, uMessage, wParam, lParam);
}
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WNDCLASSEXA wce = {sizeof(wce),
CS_DBLCLKS,
WindowProc,
0, 0,
(HINSTANCE) &__ImageBase,
(HICON) NULL,
(HCURSOR) NULL,
(HBRUSH) COLOR_BACKGROUND,
(LPCSTR) NULL,
(LPCSTR) NULL,
(HICON) NULL};
ATOM atom;
MSG msg;
HWND hWindow;
DWORD dwError;
UINT uiClass;
CHAR szClass[256 * 3];
UINT uiTitle;
CHAR szTitle[256 * 3];
UINT uiQuirk;
CHAR szQuirk[256 * 3];
BOOL bResult;
LRESULT lResult;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
uiClass = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CLASS, sizeof(CLASS) / sizeof(*CLASS),
szClass, sizeof(szClass),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiClass == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for class name \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), CLASS, wcslen(CLASS));
else
{
uiTitle = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
TITLE, sizeof(TITLE) / sizeof(*TITLE),
szTitle, sizeof(szTitle),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiTitle == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for window title \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), TITLE, wcslen(TITLE));
else
{
wce.lpszClassName = szClass;
atom = RegisterClassExA(&wce);
if (atom == 0)
PrintConsole(hConsole,
L"RegisterClassExA() returned error %lu\n",
dwError = GetLastError());
else
{
hWindow = CreateWindowExA(WS_EX_APPWINDOW,
szClass,
szTitle,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
(HWND) NULL,
(HMENU) NULL,
(HINSTANCE) &__ImageBase,
NULL);
if (hWindow == NULL)
PrintConsole(hConsole,
L"CreateWindowExA() returned error %lu\n",
dwError = GetLastError());
else
{
uiQuirk = GetWindowTextLengthA(hWindow);
if (uiQuirk == 0)
PrintConsole(hConsole,
L"GetWindowTextLengthA() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowTextLengthA() returned %u\n",
uiQuirk);
uiQuirk = GetWindowTextA(hWindow, szQuirk, sizeof(szQuirk));
if (uiQuirk == 0)
PrintConsole(hConsole,
L"GetWindowTextA() returned error %lu\n",
dwError = GetLastError());
else
if (memcmp(szQuirk, szTitle, uiTitle) != 0)
PrintConsole(hConsole,
L"GetWindowTextA() returned DIFFERENT window title \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
uiQuirk = GetClassNameA(hWindow, szQuirk, sizeof(szQuirk));
if (uiQuirk == 0)
PrintConsole(hConsole,
L"GetClassNameA() returned error %lu\n",
dwError = GetLastError());
else
if (memcmp(szQuirk, szClass, uiClass) != 0)
PrintConsole(hConsole,
L"GetClassNameA() returned DIFFERENT class name \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
uiQuirk = RealGetWindowClassA(hWindow, szQuirk, sizeof(szQuirk));
if (uiQuirk == 0)
PrintConsole(hConsole,
L"RealGetWindowClassA() returned error %lu\n",
dwError = GetLastError());
else
if (memcmp(szQuirk, szClass, uiClass) != 0)
PrintConsole(hConsole,
L"RealGetWindowClassA() returned DIFFERENT class name \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
while ((bResult = GetMessageA(&msg, (HWND) NULL, 0, 0)) > 0)
{
if (TranslateMessage(&msg))
;
lResult = DispatchMessageA(&msg);
}
dwError = bResult < 0 ? GetLastError() : msg.wParam;
}
if (!UnregisterClassA(szClass, (HINSTANCE) &__ImageBase))
PrintConsole(hConsole,
L"UnregisterClassA() returned error %lu\n",
dwError = GetLastError());
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Note: the character strings CLASS
and
TITLE
contain 123 respectively
15 × 17 = 255 (wide) characters,
equivalent to 123 and 255
ANSI
characters in 123 and 255 bytes or 123 and 255
Unicode
code points in
123 × 2 + 17 = 263 and
255 × 3 = 765
UTF-8
code units alias bytes.
Build the console application quirk41.exe
from the
source file quirk41.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk41.c kernel32.lib user32.libFor 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: quirk41.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk41.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk41.exe quirk41.obj kernel32.lib user32.lib
Execute the console application quirk41.exe
built in
step 2. to show the behaviour with the legacy
code page,
then close its window:
VER .\quirk41.exe
Microsoft Windows [Version 10.0.22621.1105] GetWindowTextLengthA() returned 255
Create the text file quirk41.xml
with the following
content next to the console application quirk41.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk41' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk41 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
application manifest
quirk41.xml
created in step 4. in the console
application quirk41.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk41.xml /OUTPUTRESOURCE:quirk41.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk41.exe
configured
in step 5. for
code page
65001 alias CP_UTF
to demonstrate the
(mis)behaviour bug, then
close its window:
VER .\quirk41.exe
Microsoft Windows [Version 10.0.22621.1105]
GetWindowTextLengthA() returned 765
GetClassNameA() returned DIFFERENT class name '€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö' of 245 characters
OUCH: the Win32 function
GetClassNameA()
returns a truncated character string of only 114
code points in 245 code units instead of 123 code points in 263
code units!
Gatesparticle with 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):