Search code examples
cwindowswinapidllloadlibrary

LoadLibraryExW Fails to Load User32.dll


When trying to load C:\Windows\System32\user32.dll via LoadLibraryExW, it fails with the last error of ERROR_INVALID_IMAGE_HASH.

Here is how it is loaded:

HMODULE User32Lib = LoadLibraryExW(L"C:\\Windows\\System32\\user32.dll", NULL, LOAD_LIBRARY_REQUIRE_SIGNED_TARGET);

I looked at the DLL itself, and it was signed (for the version on my machine) on 8 April 2020 so it should still be valid.

Am I doing something incorrectly?


Solution

  • Apparently LOAD_LIBRARY_REQUIRE_SIGNED_TARGET requires the PE image to be linked with IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY (0x0080) in its DLL characteristics. This is a flag that forces the memory manager in the kernel to check for a digital signature when loading the image. Refer to the linker option /INTEGRITYCHECK.

    Most of the system DLLs do not have this characteristic. "user32.dll" doesn't have it, but "bcrypt.dll" does:

    PS C:\> $user32_hdr = get-peheader C:\Windows\System32\user32.dll
    PS C:\> $bcrypt_hdr = get-peheader C:\Windows\System32\bcrypt.dll
    PS C:\> '{0:x}' -f $user32_hdr.DllCharacteristics
    4160
    PS C:\> '{0:x}' -f $bcrypt_hdr.DllCharacteristics
    41E0
    

    I don't know much in particular about the subject of code signing and the implementation details in the loader and memory manager. I just used a debugger to discover that the load was failing with STATUS_INVALID_IMAGE_HASH in LdrpCompleteMapModule, after it checked for 0x80 in the DLL characteristics. From there I searched for discussions on this value and the /integritycheck option in relation to LOAD_LIBRARY_REQUIRE_SIGNED_TARGET. I found a few unofficial references that claimed the latter requires the former. So I wrote a script to dump the DLL characteristics of system DLLs in order to find one that has the IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY flag. Having found "bcrypt.dll" and checked that it wasn't already loaded, I confirmed that loading it with LOAD_LIBRARY_REQUIRE_SIGNED_TARGET does work.