Hi @ll,
(a long[er] form of the following advisory is available at
<https://skanthak.homepage.t-online.de/snafu.html>)
With Windows 10 1607, Microsoft introduced the /DEPENDENTLOADFLAG
linker option, a security feature to restrict or limit the search
path for DLLs:
| 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.
[*] DLL planting attacks referred to "Dynamic-Link Library Security"
<https://msdn.microsoft.com/en-us/library/ff919712.aspx>
The above quote was taken from
<https://docs.microsoft.com/en-us/cpp/build/reference/dependentloadflag>
before 2020-01-22; according to it /DEPENDENTLOADFLAG applies to
RUNTIME linking via LoadLibrary() ... which it but WRONG.
Demonstration:
~~~~~~~~~~~~~~
0. on a current installation of Windows 10, start the command prompt
of the Windows Development Kit and run the following two commands:
Set CL=/Iwindows.h /W4 /Zl
Set LINK=/DEPENDENTLOADFLAG:0x800 /DYNAMICBASE /NXCOMPAT /RELEASE /SUBSYSTEM:CONSOLE
1. build a minimal SNAFU.DLL from the following source file SNAFU.C
__declspec(dllexport)
BOOL WINAPI _DllMainCRTStartup(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
return TRUE;
}
with the following command:
CL.EXE /LD SNAFU.C /link /ENTRY:_DllMainCRTStartup /EXPORT:_DllMainCRTStartup
2. build a minimal application SNAFU.EXE from the following source
file SNAFU.C
__declspec(noreturn)
VOID WINAPI mainCRTStartup(VOID)
{
HMODULE hModule = LoadLibraryA("SNAFU.DLL");
if (hModule == NULL)
ExitProcess(GetLastError());
if (!FreeLibrary(hModule))
ExitProcess(GetLastError());
ExitProcess(0);
}
with the following command:
CL.EXE SNAFU.C /link /DEFAULTLIB:kernel32.lib /ENTRY:mainCRTStartup
3. run the application SNAFU.EXE and display its exit code with
the following commands:
.\SNAFU.EXE
Echo %ERRORLEVEL%
The exit code is 0, proving that /DEPENDENTLOADFLAG:0x800
does NOT limit the DLL search path for LoadLibrary() to
%SystemRoot%\System32\!
4. when you change the return value of the DLL's entry point
function _DllMainCRTStartup() to FALSE, the exit code is
1114 alias ERROR_DLL_INIT_FAILED, again proving that
/DEPENDENTLOADFLAG:0X800 does NOT work as documented above.
Due to its security impact (see "Dynamic-Link Library Security")
I reported this bug (plus two bugs in LINK.EXE, which fails to
set /DEPENDENTLOADFLAG in executable files; see the full story at
<https://skanthak.homepage.t-online.de/snafu.html>) to Microsoft's
Security Response Center, where MSRC Case 56011 was opened.
They replied to the bug demonstrated above with the following
statement, IGNORING the bugs reported against LINK.EXE completely:
| 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.
On 2020-01-24 the documentation update went live; it now reads:
| 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.
The changed documentation is but STILL wrong, /DEPENDENTLOADFLAG
also FAILS to restrict the DLL search path for LOADTIME linking.
JFTR: for the definitions of RUNTIME linking and LOADTIME linking,
see <https://msdn.microsoft.com/en-us/library/ms685090.aspx>
and <https://msdn.microsoft.com/en-us/library/ms684184.aspx>
Demonstration (continued):
~~~~~~~~~~~~~~~~~~~~~~~~~~
5. build another minimal application SNAFU.EXE from the following
source file SNAFU.C
__declspec(dllimport)
extern BOOL WINAPI _DllMainCRTStartup(HANDLE hModule, DWORD dwReason, LPVOID lpReserved);
__declspec(noreturn)
VOID WINAPI mainCRTStartup(VOID)
{
ExitProcess(_DllMainCRTStartup != NULL);
}
with the following command:
CL.EXE SNAFU.C SNAFU.LIB /link /DEFAULTLIB:kernel32.lib /ENTRY:mainCRTStartup
6. run the second application SNAFU.EXE and display its exit code
with the following commands:
.\SNAFU.EXE
Echo %ERRORLEVEL%
The exit code is 0, proving that /DEPENDENTLOADFLAG:... does
NOT limit the DLL search path for Windows' module loader!
7. When you change the return value of the DLL's entry point
function _DllMainCRTStartup() to FALSE, Windows module loader
shows a message box and the exit code is 0xC0000142 alias
STATUS_DLL_INIT_FAILED, again proving that
/DEPENDENTLOADFLAG:0x800 does NOT work as documented!
8. When you erase SNAFU.DLL and run SNAFU.EXE, Windows module
loader shows a message box and the exit code is 0xC0000135
alias STATUS_DLL_NOT_FOUND, which is the expected behaviour
if /DEPENDENTLOADFLAG:0x800 would work as documented and limit
the DLL search path to %SystemRoot%\System32\
stay tuned, and don't trust unverified or incomplete documentation
Stefan Kanthak