debuggingwinapidlldllimportloadlibrary

Diagnosing delay import failure of DPAPI.DLL in CRYPT32.DLL


I have an application that simply calls the CryptProtectData() function:

CryptProtectData(...);

This call returns FALSE and the GetLastError() function returns 127 (0x7F), which is ERROR_PROC_NOT_FOUND.

The CryptProtectData() function is located in CRYPT32.DLL, so I used PE Explorer to see its delay imports. The app says CRYPT32.DLL delay-imports the CryptProtectDataNoUI() function from DPAPI.DLL. This sounds like a hit.

So, I made the (only) following change:

LoadLibrary("dpapi.dll");
CryptProtectData(...);

Now the code executes successfully.

If I remove the call to LoadLibrary() then CryptProtectData() fails again.

I don't see any explanation for this behaviour. I can see that when there are no calls to LoadLibrary(), CRYPT32.DLL does not load DPAPI.DLL (there is no notification in the IDE's debugger). So, it is definitely not about GetProcAddress() (which is never called), it is about the DLL. It can't be about the DLL search path, as LoadLibrary() loads the DLL just fine by just its name (no path is specified).

Why does CRYPT32.DLL fail to load the DPAPI.DLL library? How do I diagnose this?

I mean, I could use the CPU debugger to trace into what NTDLL.DLL.ResolveDelayLoadedAPI() is doing, but it is quite a complex function. Is there an easier way?


UPDATE:

A brief analysis indicates LdrpLoadDllInternal() receives the "DPAPI.dll" string in both cases. The call stack is:

ntdll.dll LdrpLoadDllInternal
ntdll.dll LdrpLoadForwardedDll
ntdll.dll LdrpGetDelayloadExportDll
ntdll.dll LdrpHandleProtectedDelayload
ntdll.dll LdrResolveDelayLoadedAPI
crypt32.dll CryptProtectData

LdrpLoadDllInternal() calls LdrpFastpthReloadedDll():

Next, I tried test code #3:

LoadLibrary("dpapi.dll");

and compared it with test code #1. Now LdrpFastpthReloadedDll() returns 0xC0000135 (STATUS_DLL_NOT_FOUND) for test code #1 and test code #3, indicating the requested DLL is not loaded, so execution continues.

LdrpFindOrPrepareLoadingModule() is called next - which returns 0xC0000135 (STATUS_DLL_NOT_FOUND) for both test code #1 and test code #3. So LdrpProcessWork() is called next. It calls LdrpMapDllSearchPath() in both cases:

Looking inside LdrpMapDllSearchPath(): it calls LdrpSearchPath(), which returns the same return code (0 for code #3 and 0xC0000135 for code #1).

Armed with this info, I tried the following:

SetDllDirectory("C:\\Windows\\System32\\");
CryptProtectData(...); // works

and

SetDllDirectory(NULL);
CryptProtectData(...); // fails

So, it appears to be an issue about the DLL search path after all. But the exact cause is still unknown. As well as why LoadLibrary() succeeds.


Solution

  • I found out the method to diagnosing delay-import errors is the same method as diagnosing LoadLibrary() failures:

    Get reason that LoadLibrary cannot load DLL

    Run this command once:

    gflags.exe -i testapp.exe +sls
    

    and then run your app under the debugger. The system will use OutputDebugString() to log what it is doing.

    For example, test code #1 gives this log:

    47cc:35c8 @ 201777953 - LdrpLoadDllInternal - ENTER: DLL name: DPAPI.dll
    47cc:35c8 @ 201777953 - LdrpFindKnownDll - ENTER: DLL name: DPAPI.dll
    47cc:35c8 @ 201777953 - LdrpFindKnownDll - RETURN: Status: 0xc0000135
    47cc:35c8 @ 201777984 - LdrpSearchPath - ENTER: DLL name: DPAPI.dll
    47cc:35c8 @ 201777984 - LdrpComputeLazyDllPath - INFO: DLL search path computed: ????\??\C:\WINDOWS\System32\KERNEL32.DLL
    47cc:35c8 @ 201778000 - LdrpResolveDllName - ENTER: DLL name: ????\??\C:\WINDOWS\System32\KERNEL32.DLL\DPAPI.dll
    47cc:35c8 @ 201778000 - LdrpResolveDllName - RETURN: Status: 0xc0000135
    47cc:35c8 @ 201778015 - LdrpSearchPath - RETURN: Status: 0xc0000135
    47cc:35c8 @ 201778015 - LdrpProcessWork - ERROR: Unable to load DLL: "DPAPI.dll", Parent Module: "C:\WINDOWS\System32\CRYPT32.dll", Status: 0xc0000135
    47cc:35c8 @ 201778031 - LdrpLoadDllInternal - RETURN: Status: 0xc0000135
    47cc:35c8 @ 201778031 - LdrpRedirectDelayloadFailure - ERROR: Failed to find export DPAPI.dll!CryptProtectDataNoUI (Ordinal:0) in "CRYPT32.dll"  0xc0000135
    

    test code #3 gives this log:

    4c74:4b98 @ 202122937 - LdrLoadDll - ENTER: DLL name: dpapi.dll
    4c74:4b98 @ 202122937 - LdrpLoadDllInternal - ENTER: DLL name: dpapi.dll
    4c74:4b98 @ 202122953 - LdrpFindKnownDll - ENTER: DLL name: dpapi.dll
    4c74:4b98 @ 202122953 - LdrpFindKnownDll - RETURN: Status: 0xc0000135
    4c74:4b98 @ 202122968 - LdrpSearchPath - ENTER: DLL name: dpapi.dll
    4c74:4b98 @ 202122968 - LdrpComputeLazyDllPath - INFO: DLL search path computed: C:\TestApp;C:\WINDOWS\SYSTEM32;C:\WINDOWS\system;C:\WINDOWS;...cut...
    4c74:4b98 @ 202122984 - LdrpResolveDllName - ENTER: DLL name: C:\TestApp\dpapi.dll
    4c74:4b98 @ 202123000 - LdrpResolveDllName - RETURN: Status: 0xc0000135
    4c74:4b98 @ 202123000 - LdrpResolveDllName - ENTER: DLL name: C:\WINDOWS\SYSTEM32\dpapi.dll
    4c74:4b98 @ 202123015 - LdrpResolveDllName - RETURN: Status: 0x00000000
    4c74:4b98 @ 202123031 - LdrpSearchPath - RETURN: Status: 0x00000000
    4c74:4b98 @ 202123031 - LdrpMinimalMapModule - ENTER: DLL name: C:\WINDOWS\SYSTEM32\dpapi.dll
    Module Load: DPAPI.dll. No Debug Info. Base Address: $6F170000. 
    4c74:4b98 @ 202123062 - LdrpMinimalMapModule - RETURN: Status: 0x00000000
    

    Unfortunately, there is not enough details to understand what is wrong. The issue seems to be related to LdrpComputeLazyDllPath(). I still need to find out why the DLL search path is correct when LoadLibrary() is called, but is not correct when delay-loading is used.