Microsoft Windows 10 DLL Search Path

2020.02.01
Risk: Medium
Local: Yes
Remote: No
CVE: N/A
CWE: N/A

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


Vote for this issue:
0%
100%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top