Defense in depth -- the Microsoft way (part 61): security features are built to fail (or documented wrong)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Linux Security]     [Netfilter]     [PHP]     [Yosemite News]     [Linux Kernel]

  Powered by Linux