Valid HTML 4.01 Transitional Valid CSS Valid SVG 1.0

Me, myself & IT

Bugs in Module Loader of Windows 10 and LINK.EXE Version 14.1*

Introduction
Manifestation
Demonstration
Security Impact
MSRC Case 56011
Follow-Up
Demonstration (continued)
Demonstration (continued)
Demonstration (completed)

Introduction

Windows 10 1607 alias Anniversary Update, codenamed Redstone 1, introduced the security feature to modify and restrict the search path for the Win32 function LoadLibrary() through the /DEPENDENTLOADFLAG § linker option:
/DEPENDENTLOADFLAG[:load_flags]
[…]
On supported operating systems, this option has the effect of changing calls to LoadLibrary("dependent.dll") to the equivalent of LoadLibraryEx("dependent.dll", 0, load_flags).
[…]
This flag can be used to make DLL planting attacks more difficult.
[…]
An option of /DEPENDENTLOADFLAG:0x800 is even more restrictive, limiting search to the %windows%\system32 directory.
Note: the text cited above shows the original and wrong documentation, present until January 24, 2020.

§ This option is supported by LINK.EXE since version 14.10, shipped with Visual Studio 2017 and newer versions; to use the security feature with prior versions of LINK.EXE, provide an initialised (constant) IMAGE_LOAD_CONFIG_DIRECTORY structure with public name _load_config_used and its DependentLoadFlags (former Reserved1) member set to the desired value.

Caveat: older versions of LINK.EXE set a wrong size for the IMAGE_LOAD_CONFIG_DIRECTORY structure in the IMAGE_DATA_DIRECTORY array; Windows’ module loader but discards the /DEPENDENTLOADFLAG in portable executables for the AMD64 alias x64 processor architecture when this size doesn’t match the size of the _load_config_used structure stored in its first member!

The values for /DEPENDENTLOADFLAG are documented with the LoadLibraryEx() function:

[…]
LOAD_LIBRARY_SEARCH_APPLICATION_DIR
0x00000200
If this value is used, the application's installation directory is searched for the DLL and its dependencies. Directories in the standard search path are not searched. […]
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
0x00001000
This value is a combination of LOAD_LIBRARY_SEARCH_APPLICATION_DIR, LOAD_LIBRARY_SEARCH_SYSTEM32, and LOAD_LIBRARY_SEARCH_USER_DIRS. Directories in the standard search path are not searched. […]
[…]
LOAD_LIBRARY_SEARCH_SYSTEM32
0x00000800
If this value is used, %windows%\system32 is searched for the DLL and its dependencies. Directories in the standard search path are not searched. […]
LOAD_LIBRARY_SEARCH_USER_DIRS
0x00000400
If this value is used, directories added using the AddDllDirectory or the SetDllDirectory function are searched for the DLL and its dependencies. If more than one directory has been added, the order in which the directories are searched is unspecified. Directories in the standard search path are not searched. […]
[…]

If more than one LOAD_LIBRARY_SEARCH flag is specified, the directories are searched in the following order:

Especially notice the highlighted statement common to all flag descriptions there: directories in the standard search path are not searched.

Note: the MSDN article Dynamic-Link Library Search Order documents the standard search path.

Manifestation

This security feature but does not work as advertised, the bugs render it void: /DEPENDENTLOADFLAG:… is ignored completely and no restriction is applied to the search path for the Win32 function LoadLibrary()!

Demonstration

Perform the following 5 simple steps to demonstrate the bugs.
  1. Create the text file SNAFU.C with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2016-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #ifdef _DLL
    const	LPCSTR	szReason[4] = {"DLL_PROCESS_DETACH\n",
    		               "DLL_PROCESS_ATTACH\n",
    		               "DLL_THREAD_ATTACH\n",
    		               "DLL_THREAD_DETACH\n"};
    
    BOOL	WINAPI	_DllMainCRTStartup(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
    {
    #ifdef VERBOSE
    	DWORD	dwConsole;
    
    	WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), szReason[dwReason], lstrlenA(szReason[dwReason]), &dwConsole, NULL);
    #else
    	OutputDebugStringA(szReason[dwReason]);
    #endif
    	return FALSE;
    }
    
    __declspec(dllexport)
    const	CHAR	SNAFU[] = "Situation normal, all\b\b\bbut \"/DEPENDENTLOADFLAG\" fucked up!\n";
    #else
    #ifdef LOADTIME
    __declspec(dllimport)
    extern	const	CHAR	SNAFU[];
    
    __declspec(noreturn)
    VOID	CDECL	mainCRTStartup(VOID)
    {
    #ifdef VERBOSE
    	DWORD	dwConsole;
    
    	WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), SNAFU, lstrlenA(SNAFU), &dwConsole, NULL);
    #else
    	OutputDebugStringA(SNAFU);
    #endif
    	ExitProcess(0);
    }
    #else
    __declspec(noreturn)
    VOID	CDECL	mainCRTStartup(VOID)
    {
    	DWORD	dwError = ERROR_SUCCESS;
    	HMODULE	hModule = LoadLibraryA("SNAFU.DLL");
    
    	if (hModule == NULL)
    		dwError = GetLastError();
    	else
    		if (!FreeLibrary(hModule))
    			dwError = GetLastError();
    
    	ExitProcess(dwError);
    }
    #endif // LOADTIME
    #endif // _DLL
    
    #ifdef LCU
    #ifndef _WIN64
    const	struct	_IMAGE_LOAD_CONFIG_DIRECTORY_32
    {
    	DWORD	Size;
    	DWORD	TimeDateStamp;
    	WORD	MajorVersion;
    	WORD	MinorVersion;
    	DWORD	GlobalFlagsClear;
    	DWORD	GlobalFlagsSet;
    	DWORD	CriticalSectionDefaultTimeout;
    	DWORD	DeCommitFreeBlockThreshold;
    	DWORD	DeCommitTotalFreeThreshold;
    	DWORD	LockPrefixTable;
    	DWORD	MaximumAllocationSize;
    	DWORD	VirtualMemoryThreshold;
    	DWORD	ProcessHeapFlags;
    	DWORD	ProcessAffinityMask;
    	WORD	CSDVersion;
    #if LCU > 2				// Redstone 1 (1607)
    	WORD	DependentLoadFlags;
    #else
    	WORD	Reserved1;
    #endif
    	DWORD	EditList;
    	DWORD	SecurityCookie;
    	DWORD	SEHandlerTable;
    	DWORD	SEHandlerCount;
    #if LCU > 0				// Threshold 1 (1507)
    	DWORD	GuardCFCheckFunctionPointer;
    	DWORD	GuardCFDispatchFunctionPointer;
    	DWORD	GuardCFFunctionTable;
    	DWORD	GuardCFFunctionCount;
    	DWORD	GuardFlags;
    #if LCU > 1				// Threshold 2 (1511)
    	struct	// _IMAGE_LOAD_CONFIG_CODE_INTEGRITY
    	{
    		WORD	Flags;
    		WORD	Catalog;
    		DWORD	CatalogOffset;
    		DWORD	Reserved;
    	} CodeIntegrity;
    #if LCU > 2				// Redstone 1 (1607)
    	DWORD	GuardAddressTakenIatEntryTable;
    	DWORD	GuardAddressTakenIatEntryCount;
    	DWORD	GuardLongJumpTargetTable;
    	DWORD	GuardLongJumpTargetCount;
    	DWORD	DynamicValueRelocTable;
    	DWORD	CHPEMetadataPointer;
    #if LCU > 3				// Redstone 2 (1703)
    	DWORD	GuardRFFailureRoutine;
    	DWORD	GuardRFFailureRoutineFunctionPointer;
    	DWORD	DynamicValueRelocTableOffset;
    	WORD	DynamicValueRelocTableSection;
    	WORD	Reserved2;
    	DWORD	GuardRFVerifyStackPointerFunctionPointer;
    	DWORD	HotPatchTableOffset;
    #if LCU > 4				// Redstone 3 (1709)
    	DWORD	Reserved3;
    	DWORD	EnclaveConfigurationPointer;
    #if LCU > 5				// Redstone 4 (1803)
    	DWORD	VolatileMetadataPointer;
    #if LCU > 6				// Redstone 5 (1809)
    	DWORD	GuardEHContinuationTable;
    	DWORD	GuardEHContinuationCount;
    #if LCU > 7				// Vibranium 3 (21H1)
    	DWORD	GuardXFGCheckFunctionPointer;
    	DWORD	GuardXFGDispatchFunctionPointer;
    	DWORD	GuardXFGTableDispatchFunctionPointer;
    #if LCU > 8				// Vibranium 4 (21H2)
    	DWORD	CastGuardOsDeterminedFailureMode;
    #if LCU > 9				// Vibranium 5 (22H2)
    	DWORD	GuardMemcpyFunctionPointer;
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    } _load_config_used = {sizeof(_load_config_used), 'OUCH', _MSC_VER / 100, _MSC_VER % 100};
    #else
    const	struct	_IMAGE_LOAD_CONFIG_DIRECTORY_64
    {
    	DWORD	Size;
    	DWORD	TimeDateStamp;
    	WORD	MajorVersion;
    	WORD	MinorVersion;
    	DWORD	GlobalFlagsClear;
    	DWORD	GlobalFlagsSet;
    	DWORD	CriticalSectionDefaultTimeout;
    	DWORD64	DeCommitFreeBlockThreshold;
    	DWORD64	DeCommitTotalFreeThreshold;
    	DWORD64	LockPrefixTable;
    	DWORD64	MaximumAllocationSize;
    	DWORD64	VirtualMemoryThreshold;
    	DWORD64	ProcessAffinityMask;
    	DWORD	ProcessHeapFlags;
    	WORD	CSDVersion;
    #if LCU > 2				// Redstone 1 (1607)
    	WORD	DependentLoadFlags;
    #else
    	WORD	Reserved1;
    #endif
    	DWORD64	EditList;
    	DWORD64	SecurityCookie;
    	DWORD64	SEHandlerTable;
    	DWORD64	SEHandlerCount;
    #if LCU > 0				// Threshold 1 (1507)
    	DWORD64	GuardCFCheckFunctionPointer;
    	DWORD64	GuardCFDispatchFunctionPointer;
    	DWORD64	GuardCFFunctionTable;
    	DWORD64	GuardCFFunctionCount;
    	DWORD	GuardFlags;
    #if LCU > 1				// Threshold 2 (1511)
    	struct	// _IMAGE_LOAD_CONFIG_CODE_INTEGRITY
    	{
    		WORD	Flags;
    		WORD	Catalog;
    		DWORD	CatalogOffset;
    		DWORD	Reserved;
    	} CodeIntegrity;
    #if LCU > 2				// Redstone 1 (1607)
    	DWORD64	GuardAddressTakenIatEntryTable;
    	DWORD64	GuardAddressTakenIatEntryCount;
    	DWORD64	GuardLongJumpTargetTable;
    	DWORD64	GuardLongJumpTargetCount;
    	DWORD64	DynamicValueRelocTable;
    	DWORD64	CHPEMetadataPointer;
    #if LCU > 3				// Redstone 2 (1703)
    	DWORD64	GuardRFFailureRoutine;
    	DWORD64	GuardRFFailureRoutineFunctionPointer;
    	DWORD	DynamicValueRelocTableOffset;
    	WORD	DynamicValueRelocTableSection;
    	WORD	Reserved2;
    	DWORD64	GuardRFVerifyStackPointerFunctionPointer;
    	DWORD	HotPatchTableOffset;
    #if LCU > 4				// Redstone 3 (1709)
    	DWORD	Reserved3;
    	DWORD64	EnclaveConfigurationPointer;
    #if LCU > 5				// Redstone 4 (1803)
    	DWORD64	VolatileMetadataPointer;
    #if LCU > 6				// Redstone 5 (1809)
    	DWORD64	GuardEHContinuationTable;
    	DWORD64	GuardEHContinuationCount;
    #if LCU > 7				// Vibranium 3 (21H1)
    	DWORD64	GuardXFGCheckFunctionPointer;
    	DWORD64	GuardXFGDispatchFunctionPointer;
    	DWORD64	GuardXFGTableDispatchFunctionPointer;
    #if LCU > 8				// Vibranium 4 (21H2)
    	DWORD64	CastGuardOsDeterminedFailureMode;
    #if LCU > 9				// Vibranium 5 (22H2)
    	DWORD64	GuardMemcpyFunctionPointer;
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    #endif
    } _load_config_used = {sizeof(_load_config_used), 'OUCH', _MSC_VER / 100, _MSC_VER % 100};
    #endif // _WIN64
    #endif // LCU
    Note: the entry-point function _DllMainCRTStartup() intentionally returns FALSE to force an error on DLL load!
  2. Build the DLL SNAFU.DLL and the console application SNAFU.EXE from the source file SNAFU.C created in step 1. for the i386 alias x86 processor architecture:

    SET CL=/GA /GF /Gw /Gy /W4 /wd4100 /Zl
    SET LINK=/DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /LARGEADDRESSAWARE /MACHINE:I386 /NOCOFFGRPINFO /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    CL.EXE /LD /MD SNAFU.C
    CL.EXE SNAFU.C
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: both 32-bit portable executables build 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 19.16.27027.1 for x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /LARGEADDRESSAWARE /MACHINE:I386 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.dll
    /dll
    /implib:SNAFU.lib
    SNAFU.obj
       Creating library SNAFU.lib and object SNAFU.exp
    
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 19.16.27027.1 for x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /LARGEADDRESSAWARE /MACHINE:I386 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.exe
    SNAFU.obj
  3. Check whether at least the console application SNAFU.EXE built in step 2. has /DEPENDENTLOADFLAG set:

    LINK.EXE /DUMP /HEADERS /LOADCONFIG SNAFU.EXE
    Microsoft (R) COFF/PE Dumper Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file SNAFU.EXE
    
    PE signature found
    
    File Type: EXECUTABLE IMAGE
    
    FILE HEADER VALUES
                 14C machine (x86)
                   3 number of sections
            5DF38EE4 time date stamp Fri Dec 13 14:15:16 2019
                   0 file pointer to symbol table
                   0 number of symbols
                  E0 size of optional header
                 122 characteristics
                       Executable
                       Application can handle large (>2GB) addresses
                       32 bit word machine
    
    OPTIONAL HEADER VALUES
                 10B magic # (PE32)
               14.16 linker version
                 200 size of code
                 400 size of initialized data
                   0 size of uninitialized data
                1000 entry point (00401000)
                1000 base of code
                2000 base of data
              400000 image base (00400000 to 00403FFF)
                1000 section alignment
                 200 file alignment
               10.01 operating system version
               0.815 image version
                6.00 subsystem version
                   0 Win32 version
                4000 size of image
                 400 size of headers
                F6A9 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
                20D0 [      28] 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
                3000 [      18] 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
                2000 [      20] 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
    …
    Oops: LINK.EXE fails to generate and include the IMAGE_LOAD_CONFIG_DIRECTORY structure which holds the /DEPENDENTLOADFLAG in its DependentLoadFlags member!

    Contrary to the observed behaviour, the specification of the PE format but states:

    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.
    […]
    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.
    Ouch: even worse, LINK.EXE also fails to report this omission with an appropriate error message!

    The specification of the PE format continues with the following, completely wrong information:

    Load Configuration Layout

    The 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.
    Ouch: according to IMAGE_LOAD_CONFIG_DIRECTORY, the field at offset 0 gives the size of the structure, and the field at offset 54 (for 32-bit images) or 78 (for 64-bit images) holds the /DEPENDENTLOADFLAG!
  4. Build the console application SNAFU.EXE again from the source file SNAFU.C created in step 1., now with the preprocessor macro LCU (and in consequence a load configuration structure named _load_config_used) defined as 0, then verify that /DEPENDENTLOADFLAG is properly set:

    CL.EXE /DLCU=0 SNAFU.C
    LINK.EXE /DUMP /HEADERS /LOADCONFIG SNAFU.EXE
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 19.16.27027.1 for x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /LARGEADDRESSAWARE /MACHINE:I386 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.exe
    SNAFU.obj
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    Dump of file SNAFU.EXE
    
    PE signature found
    
    File Type: EXECUTABLE IMAGE
    
    FILE HEADER VALUES
                 14C machine (x86)
                   3 number of sections
            5DF38EE4 time date stamp Fri Dec 13 14:15:16 2019
                   0 file pointer to symbol table
                   0 number of symbols
                  E0 size of optional header
                 122 characteristics
                       Executable
                       Application can handle large (>2GB) addresses
                       32 bit word machine
    
    OPTIONAL HEADER VALUES
                 10B magic # (PE32)
               14.16 linker version
                 200 size of code
                 400 size of initialized data
                   0 size of uninitialized data
                1000 entry point (00401000)
                1000 base of code
                2000 base of data
              400000 image base (00400000 to 00403FFF)
                1000 section alignment
                 200 file alignment
               10.01 operating system version
               0.815 image version
                6.00 subsystem version
                   0 Win32 version
                4000 size of image
                 400 size of headers
                62D1 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
                2170 [      28] 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
                3000 [      18] 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
                2020 [      40] RVA [size] of Load Configuration Directory
                   0 [       0] RVA [size] of Bound Import Directory
                2000 [      20] 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 contains the following load config:
    
                00000048 size
                4F554348 time date stamp Mon Mar 5 2012 23:50:48
                   19.16 Version
                       0 GlobalFlags Clear
                       0 GlobalFlags Set
                       0 Critical Section Default Timeout
                       0 Decommit Free Block Threshold
                       0 Decommit Total Free Threshold
                00000000 Lock Prefix Table
                       0 Maximum Allocation Size
                       0 Virtual Memory Threshold
                       0 Process Heap Flags
                       0 Process Affinity Mask
                       0 CSD Version
                    0800 Dependent Load Flag
                00000000 Edit list
                00000000 Security Cookie
                00000000 Safe Exception Handler Table
                       0 Safe Exception Handler Count
    …
    Oops: notice the wrong size (0x40 instead of 0x48) for the IMAGE_LOAD_CONFIG_DIRECTORY structure in the 11th entry of the IMAGE_DATA_DIRECTORY array from the IMAGE_OPTIONAL_HEADER structure!
  5. Execute the console application SNAFU.EXE built in step 4. to demonstrate the bug in Windows’ module loader:

    .\SNAFU.EXE
    ECHO %ERRORLEVEL%
    NET.EXE HELPMSG %ERRORLEVEL%
    1114
    
    A dynamic link library (DLL) initialization routine failed.
    Ouch: the console application SNAFU.EXE returns the Win32 error code 1114 alias ERROR_DLL_INIT_FAILED instead of the expected Win32 error code 126 alias ERROR_MOD_NOT_FOUND, indicating that the Win32 function LoadLibrary() loaded the DLL SNAFU.DLL from the application directory, i.e. the search path was not limited to the system directory %SystemRoot%\System32\, and /DEPENDENTLOADFLAG:0x800 does not work as documented!
Note: repetition of the preceding 5 steps for the AMD64 alias x64 processor architecture is left as an exercise to the reader.

Note: the makefile SNAFU.MAK performs all necessary steps shown above (and below).

Security Impact

When working as advertised, /DEPENDENTLOADFLAG is were an easy to implement defense against well-known weaknesses like CWE-426: Untrusted Search Path, CWE-427: Uncontrolled Search Path Element and CWE-428: Unquoted Search Path or Element documented in the CWE, which allow well-known attacks like CAPEC-471: Search Order Hijacking documented in the CAPEC, also known as DLL spoofing, DLL preloading, directory poisoning, binary planting, DLL hijacking and DLL side-loading.

The posts MS09-014: Addressing the Safari Carpet Bomb vulnerability, More information about the DLL Preloading remote attack vector, An update on the DLL-preloading remote attack vector and Triaging a DLL planting vulnerability on Microsoft’s Security Research and Defense Blog give additional information on this ubiquituous vulnerability.

MSRC Case 56011

Due to their security impact I reported these bugs to the MSRC, where case number 56011 was assigned.

They replied with the following statements:

The team has finished their investigation and determined the way they will address this report is via a documentation update of https://docs.microsoft.com/en-us/cpp/build/reference/dependentloadflag?view=vs-2019.

It wasn't supposed to say that LoadLibrary will act as LoadLibraryEx, specifically this statement:

On supported operating systems, this option has the effect of changing calls to LoadLibrary("dependent.dll") to the equivalent of LoadLibraryEx("dependent.dll", 0, load_flags). Calls to LoadLibraryEx are unaffected. This option doesn't apply recursively to DLLs loaded by your app.

Note: there’s no statement regarding the multiple bugs in LINK.EXE!

Follow-Up

According to the documentation changed in response to my report on January 24, 2020, /DEPENDENTLOADFLAG is now supposed to affect (static) load-time linking instead of (dynamic) run-time linking:
Sets the default load flags used when the operating system resolves the statically linked imports of a module.

/DEPENDENTLOADFLAG[:load_flags]

load_flags
An optional integer value that specifies the load flags to apply when resolving statically linked import dependencies of the module. The default value is 0. For a list of supported flag values, see the LOAD_LIBRARY_SEARCH_* entries in LoadLibraryEx.
[…]
[…] if you specify the link option /DEPENDENTLOADFLAG:0x800 (the value of the flag LOAD_LIBRARY_SEARCH_SYSTEM32), then the module search path is limited to the %windows%\system32 directory.

Note: the documentation still lacks (necessary) information similar to that given for the /SAFESEH linker option:
If you link with /NODEFAULTLIB and you want a table of safe exception handlers, you need to supply a load config struct (such as can be found in loadcfg.c CRT source file) that contains all the entries defined for Visual C++. For example: […]
Note: this also applies when the /Zl compiler option is used!

Demonstration (continued)

Perform the following additional 2 simple steps to prove the changed documentation wrong too.
  1. Build the console application SNAFU.COM from the source file SNAFU.C created in step 1., now with the preprocessor macros LCU and LOADTIME defined:

    CL.EXE /DLCU=0 /DLOADTIME /FeSNAFU.COM SNAFU.C SNAFU.LIB
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: the 32-bit portable executable SNAFU.COM builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 19.16.27027.1 for x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /LARGEADDRESSAWARE /MACHINE:I386 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.COM
    SNAFU.obj
    SNAFU.LIB
  2. Execute the console application SNAFU.COM built in step 6. to demonstrate the bug in Windows’ module loader:

    .\SNAFU.COM
    ECHO %ERRORLEVEL%
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    [Screen shot of error message for NTSTATUS 0xC0000142 alias STATUS_DLL_INIT_FAILED]
    -1073741502
    0xc0000142 (NT: 0xc0000142 STATUS_DLL_INIT_FAILED) -- 3221225794 (-1073741502)
    Error message text: {DLL Initialization Failed}
    Initialization of the dynamic link library %hs failed. The process is terminating abnormally.
    CertUtil: -error command completed successfully.
    Ouch: -1073741502 is the decimal representation of 0xC0000142 alias STATUS_DLL_INIT_FAILED, indicating that SNAFU.DLL is loaded from the application directory, i.e. the search path was not limited to the system directory %SystemRoot%\System32\, and /DEPENDENTLOADFLAG:0x800 does not work as documented; the expected error message and exit code is but 0xC0000135 alias STATUS_DLL_NOT_FOUND.

Demonstration (continued)

Perform the following additional 2 simple steps to show the missing piece of the changed documentation, respectively the braindead implementation (detail) of this security feature in Windows’ module loader.
  1. Build the console application SNAFU.COM from the source file SNAFU.C created in step 1. again, now with the preprocessor macro LCU defined as 1 or greater, and LOADTIME defined too:

    CL.EXE /DLCU=1 /DLOADTIME /FeSNAFU.COM SNAFU.C SNAFU.LIB
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: the 32-bit portable executable SNAFU.COM builds without the MSVCRT libraries.

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 19.16.27027.1 for x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /LARGEADDRESSAWARE /MACHINE:I386 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.COM
    SNAFU.obj
    SNAFU.LIB
  2. Execute the console application SNAFU.COM built in step 8. to demonstrate the bug fix:

    .\SNAFU.COM
    ECHO %ERRORLEVEL%
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    [Screen shot of error message for NTSTATUS 0xC0000135 alias STATUS_DLL_NOT_FOUND]
    -1073741515
    0xc0000135 (NT: 0xc0000135 STATUS_DLL_NOT_FOUND) -- 3221225781 (-1073741515)
    Error message text: {Unable To Locate Component}
    This application has failed to start because %hs was not found. Reinstalling the application might fix this problem.
    CertUtil: -error command completed successfully.
    -1073741515 is the decimal representation of the expected error message and exit code 0xC0000135 alias STATUS_DLL_NOT_FOUND, indicating that the application directory was removed from the search path.
Conclusion: in order to honor the /DEPENDENTLOADFLAG linker option in the i386 alias x86 execution environment, Windows’ module loader expects an IMAGE_LOAD_CONFIG_DIRECTORY which includes the GuardFlags member at least!

Note: the members GuardCFCheckFunctionPointer to GuardFlags were added with Windows 8.1 to the IMAGE_LOAD_CONFIG_DIRECTORY structure; the member DependentLoadFlags alias Reserved1 is but present there since Windows NT 3.1!

Note: repetition of the preceding 2 steps with the preprocessor macro LCU defined as 2, 3, 4, 5, 6, 7, 8, 9 and 10 is left as an exercise to the reader.

Demonstration (completed)

Perform the following final 4 simple steps to verify the behaviour of this security feature in the AMD64 alias x64 execution environment.
  1. Build the DLL SNAFU.DLL and the console application SNAFU.COM from the source file SNAFU.C created in step 1. for the AMD64 alias x64 processor architecture:

    SET CL=/GA /GF /Gw /Gy /W4 /wd4100 /Zl
    SET LINK=/DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /HIGHENTROPYVA /MACHINE:AMD64 /NOCOFFGRPINFO /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    CL.EXE /LD /MD SNAFU.C
    CL.EXE /DLCU=0 /DLOADTIME /FeSNAFU.COM SNAFU.C SNAFU.LIB
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: both 64-bit portable executables build without the MSVCRT libraries.

    Note: the command lines can be copied and pasted as block into a Command Processor window!

    Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27027.1 for x64
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /HIGHENTROPYVA /MACHINE:AMD64 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.dll
    /dll
    /implib:SNAFU.lib
    SNAFU.obj
       Creating library SNAFU.lib and object SNAFU.exp
    
    Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27027.1 for x64
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /HIGHENTROPYVA /MACHINE:AMD64 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.COM
    SNAFU.obj
    SNAFU.LIB
  2. Execute the console application SNAFU.COM built in step 10. to demonstrate the bug in Windows’ module loader:

    .\SNAFU.COM
    ECHO %ERRORLEVEL%
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    [Screen shot of error message for NTSTATUS 0xC0000142 alias STATUS_DLL_INIT_FAILED]
    -1073741502
    0xc0000142 (NT: 0xc0000142 STATUS_DLL_INIT_FAILED) -- 3221225794 (-1073741502)
    Error message text: {DLL Initialization Failed}
    Initialization of the dynamic link library %hs failed. The process is terminating abnormally.
    CertUtil: -error command completed successfully.
    Ouch: -1073741502 is the decimal representation of 0xC0000142 alias STATUS_DLL_INIT_FAILED, indicating that SNAFU.DLL is loaded from the application directory, i.e. the search path was not limited to the system directory %SystemRoot%\System32\, and /DEPENDENTLOADFLAG:0x800 does not work as documented; the expected error message and exit code is but 0xC0000135 alias STATUS_DLL_NOT_FOUND.
  3. Build the console application SNAFU.COM from the source file SNAFU.C created in step 1. a last time, now with the preprocessor macro LCU defined as 1 or greater, and LOADTIME defined too:

    CL.EXE /DLCU=1 /DLOADTIME /FeSNAFU.COM SNAFU.C SNAFU.LIB
    Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27027.1 for x64
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    SNAFU.C
    
    Microsoft (R) Incremental Linker Version 14.16.27027.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /DEFAULTLIB:KERNEL32.LIB /DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /HIGHENTROPYVA /MACHINE:AMD64 /OPT:REF /OSVERSION:10.01 /RELEASE /SUBSYSTEM:CONSOLE /VERSION:0.815
    /out:SNAFU.COM
    SNAFU.obj
    SNAFU.LIB
  4. Execute the console application SNAFU.COM built in step 12. to demonstrate the bug fix:

    .\SNAFU.COM
    ECHO %ERRORLEVEL%
    CERTUTIL.EXE /ERROR %ERRORLEVEL%
    [Screen shot of error message for NTSTATUS 0xC0000135 alias STATUS_DLL_NOT_FOUND]
    -1073741515
    0xc0000135 (NT: 0xc0000135 STATUS_DLL_NOT_FOUND) -- 3221225781 (-1073741515)
    Error message text: {Unable To Locate Component}
    This application has failed to start because %hs was not found. Reinstalling the application might fix this problem.
    CertUtil: -error command completed successfully.
    -1073741515 is the decimal representation of the expected error message and exit code 0xC0000135 alias STATUS_DLL_NOT_FOUND, indicating that the application directory was removed from the search path.
Conclusion: in order to honor the /DEPENDENTLOADFLAG linker option in the AMD64 alias x64 execution environment, Windows’ module loader expects an IMAGE_LOAD_CONFIG_DIRECTORY which includes the GuardFlags member at least!

Note: the members GuardCFCheckFunctionPointer to GuardFlags were added with Windows 8.1 to the IMAGE_LOAD_CONFIG_DIRECTORY structure; the member DependentLoadFlags alias Reserved1 is but present there since Windows NT 3.1!

Note: repetition of the preceding 2 steps with the preprocessor macro LCU defined as 2, 3, 4, 5, 6, 7, 8, 9 and 10 is left as an exercise to the reader.

Contact

If you miss anything here, have additions, comments, corrections, criticism or questions, want to give feedback, hints or tipps, report broken links, bugs, deficiencies, errors, inaccuracies, misrepresentations, omissions, shortcomings, vulnerabilities or weaknesses, …: don’t hesitate to contact me and feel free to ask, comment, criticise, flame, notify or report!

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.

Terms and Conditions

By using this site, you signify your agreement to these terms and conditions. If you do not agree to these terms and conditions, do not use this site!

Data Protection Declaration

This web page records no (personal) data and stores no cookies in the web browser.

The web service is operated and provided by

Telekom Deutschland GmbH
Business Center
D-64306 Darmstadt
Germany
<‍hosting‍@‍telekom‍.‍de‍>
+49 800 5252033

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):


Copyright © 1995–2024 • Stefan Kanthak • <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>