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()
:
LdrpFastpthReloadedDll()
returns 0 for test code #2 (success), indicating the requested DLL is already loaded.LdrpFastpthReloadedDll()
returns 0xC0000135 (STATUS_DLL_NOT_FOUND
) for test code #1 (failure), indicating the requested DLL is not loaded.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:
LdrpMapDllSearchPath()
returns 0 for test code #3.LdrpMapDllSearchPath()
returns 0xC0000135 (STATUS_DLL_NOT_FOUND
) for test code #1.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.
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.