UnknownDLLs, API Sets and Forwarded Exports: when Compatibility means Vulnerability
LOAD_LIBRARY_SEARCH_*
Flagssystem 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.
makefile
DETOUR.MAK
performs all necessary steps shown below.
Known DLLsfeature:
The following factors affect whether the system searches for a DLL:Note:
- […]
- If the DLL is on the list of known DLLs for the version of Windows on which the application is running, the system uses its copy of the known DLL (and the known DLL’s dependent DLLs, if any) instead of searching for the DLL. For a list of known DLLs on the current system, see the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
- […]
Known DLLsis a performance feature, it is not supposed to be a security feature; nevertheless it has but security impact!
API Setswere introduced with Windows 7; while many of them are
virtualDLLs 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.
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.New Low-Level Binaries
LOAD_LIBRARY_SEARCH_*
Flags_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
- […]
- Use the LOAD_LIBRARY_SEARCH flags with the LoadLibraryEx function, or use these flags with the SetDefaultDllDirectories function to establish a DLL search order for a process and then use the AddDllDirectory or SetDllDirectory functions to modify the list. For more information, see Dynamic-Link Library Search Order.
Windows 7, Windows Server 2008 R2, Windows Vista and Windows Server 2008: These flags and functions are available on systems with 2533623 installed.
- […]
/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 theLOAD_LIBRARY_SEARCH_*
entries in LoadLibraryEx.
[…]
[…] if you specify the link option/DEPENDENTLOADFLAG:0x800
(the value of the flagLOAD_LIBRARY_SEARCH_SYSTEM32
), then the module search path is limited to the %windows%\system32 directory.
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
!
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
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.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. 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
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.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: 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.
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.
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%
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.
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.libNote:
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
CryptSP.dll
built in step 6.:
.\CryptSP.exe CertUtil.exe /ERROR %ERRORLEVEL%
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.
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: the first sentence of the statementsNote 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 );
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
!
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
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.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: 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.
CryptBase.exe
and verify its proper function from the
exit code:
.\CryptBase.exe ECHO %ERRORLEVEL%
0
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.libNote:
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
CryptBase.dll
built in step 4.:
.\CryptBase.exe ECHO %ERRORLEVEL%
0
/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!
Perf…()
in
AdvAPI32.dll
, for
example
PerfCreateInstance()
PerfDecrementULongCounterValue()
PerfDecrementULongLongCounterValue()
PerfDeleteInstance()
PerfIncrementULongCounterValue()
PerfIncrementULongLongCounterValue()
PerfQueryInstance()
PerfSetCounterRefValue()
PerfSetCounterSetInfo()
PerfSetULongCounterValue()
PerfSetULongLongCounterValue()
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.Since[…]
ULONG PerfStopProvider( HANDLE ProviderHandle );
AdvAPI32.dll
is a
Known DLL, these function were safe to use on Windows Vista, they didn’t allow
DLL hijackingalias
DLL spoofingby planting an arbitrary
AdvAPI32.dll
in a
program’s application directorythere.
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.
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
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.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: 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.
PCWUM.exe
and verify its proper function from the exit
code:
.\PCWUM.exe ECHO %ERRORLEVEL%
0
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.libNote:
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
PCWUM.dll
built in step 4.:
.\PCWUM.exe ECHO %ERRORLEVEL%
120
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32)
and /DEPENDENTLOADFLAG:0x4000
fail to provide
sufficient protection of exports forwarded to unknownDLLs against
DLL hijackingalias
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:Ouch: it’s not theHowever, 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.
- - 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)
Known DLLsfeature 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!
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):