The LoadLibrary Explorer
There is a lot of confusion about the way LoadLibrary and LoadLibraryEx work, especially given the various security changes made in Windows 2000, Windows XP, and Server 2003 Service Packs. I needed to get the correct information about how this worked, and consequently had to create a program that actually did these calls. The results are a bit surprising. Not only is there a lot of incorrect folklore and misinterpretation out there about what is going on, but the documentation itself appears to be incorrect. Appears to be lying it its teeth, to make an understatement.
There are many factors that determine where a DLL is found during a load. They include
This program explores how LoadLibrary and LoadLlibraryEx work by allowing you to install a DLL in a specific place, or places, and then see from which place it was loaded.
Here's the documentation from the LoadLibrary documentation in the MSDN:
If SafeDllSearchMode is 1 (the default), the search order is as follows:
Windows Me/98/95: This directory does not exist.
If SafeDllSearchMode is 0, the search order is as follows:
Windows Me/98/95: This directory does not exist.
Windows XP: The default value of HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode is 0 (the current directory is searched before the system and Windows directories).
The documentation for LoadLibraryEx is equally explicit: Not only is the above stated, but in addition, there is the description
If a path is specified and dwFlags is set to LOAD_WITH_ALTERED_SEARCH_PATH, the LoadLibraryEx function uses an alternate file search strategy. The search order used depends on the setting of the HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode value.
If SafeDllSearchMode is 1 (the default), the alternate search order is as follows:
Windows Me/98/95: This directory does not exist.
If SafeDllSearchMode is 0, the alternate search order is as follows:
Windows Me/98/95: This directory does not exist.
Using the LoadLibrary Explorer, you can demonstrate that this is not the behavior you actually see in real programs on real systems. For example, if LOAD_WITH_ALTERED_SEARCH_PATH is used and an explicit path is given, and the file does not exist in that explicit path, the operation fails immediately, even if the DLL does exist in other directories specified for the search order!
When the LoadLibrary Explorer is loaded, the startup screen resembles the one shown below
This represents the startup state. Details may differ slightly in terms of initialization.
There are a set of check boxes that allow the selection of the locations into which the DLL will be placed (more later on which DLL is placed in these directories, for now assume a canonical DLL that comes with the project). When the Load button is pressed, the LoadLibrary (or LoadLibraryEx) call will be executed. If the module is successfully loaded, the library path used will be displayed, and highlighted in the controls as shown below.
In the above example, the System32, System and Executable directories are all selected. Because of the search rules in force at the time LoadLibrary was executed, the DLL is found in the executable directory.
The two viable options supported right now are LoadLibrary and LoadLibraryEx. In the above example, LoadLibrary has been selected. The other supported option is LoadLibraryEx.
Clicking the Load button will take the following actions:
The boxes display the state of the library-copy operation. Values which may be displayed include
The complete details of the DLL installation are displayed in the log window.
If the (Internal DLL) option is selected, the program uses a DLL that is built as part of the project, which is stored in the executable directory as theDLL.xxx in the executable directory.
To illustrate the effect of the "Known DLLs", the Known DLL option can be selected, which will then loadr one of the Known DLLs. Note that while a Known DLL can be installed in any of the selected directories, (except the System32 directory, which is disabled), any attempt to load a known DLL should come from the System32 directory.
The Known DLLs combo box gives the list of "known DLLs" from the key HKLM\System\CurrentControlSet\Control\Session Manager\KnownDLLs. Those DLLs with a checkmark beside the name have already been loaded into the process image, and would not be good candidates for selection to demonstrate the effect of a dynamic load.
The path, here shown as %SystemRoot%\System32, is obtained from the key HKLM\System\CurrentControlSet\Control\Session Manager\KnownDLLs\dllDirectory. It will be properly expanded before use; in the machine on which this was running it became c:\Windows\System32.
The effect of trying to load the selected urlmon.dll is shown at the left. Note that the System32 directory is disabled, and its checked status will be ignored. But in spite of this, the DLL is loaded from the Known DLLs directory, and the other directories are ignored. This demonstrates that certain forms of spoofing of these DLLs would not be possible.
There are two options supported. The first is the straight LoadLibrary call. The second is using LoadLibraryEx. When using LoadLibraryEx, the option, the flag LOAD_WITH_ALTERED_SEARCH_PATH can be selected (the other flags for LoadLibraryEx options are not supported). And here is where one of the bugs in the documentation is revealed. According to the documentation, if the LOAD_WITH_ALTERED_SEARCH_PATH option is specified, and an explicit path is given, and the DLL is not found in that directory, then the behavior reverts to the default search as if a path had not been specified. But it doesn't actually work that way!
To provide an explicit path, the Use Explicit Path Above option is used, and the options are limited to one of the directories in the list (this is sufficient for demonstrating the failure mode). With the selection shown, the generated call is
LoadLibraryEx(_T("i:\mvp_tips\LoadLibraryExplorer\debug\Working\TheDLL.dll"), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
But when the Load button is selected, the explicit path fails (note there is no check mark that causes the DLL to be placed in the selected target directory). The documented behavior, of using the search logic, fails completely.
When the program starts up, it creates a set of directories under the executable directory. These are
SetDllDirectory is available only for Windows XP Service Pack 1 and beyond and Server 2003 and beyond. It is not available on Windows 2000 or below. If the LoadLibrary Explorer is run on an earlier version of Windows, the Alternate options will be disabled. When Alternate is selected, the DLL search order is
Whenever Alternate is chosen, the DLL will be installed in the Alternate directory and SetDllDirectory will be called referencing that directory.
For any application, there is an entry in the HKEY_LOCAL_MACHINE key which indicates two pieces of information.
The key is HKLM\Software\Microsofot\Windows\CurrentVersion\App Paths\name_of_executable_file and it has two values: the (default) value and the Path value, as shown below in a screen snapshot of the Registry Editor.
Note that under the key HKLM\SOFTEARE\Microsoft\Windows\CurrentVersion\App Paths\LoadLibraryExplorer.exe, the value (Default) is given as i:\mvp_tips\LoadLibraryExplorer\debug\LoadLibraryExplorer.exe. This value is used if there is a shell shortcut that names only LoadLibraryExplorer.exe will be able to find the actual executable image. When this app is launched with ShellExecute(Ex), the Path value is checked. If it exists, its value is added to the PATH= list. If the AppPath option is checked, and these keys do not already exist, they will be created. However, there are possible inconsistencies.
If the program was launched via ShellExecute, with the Path key defined, but the App Path is not selected, the DLL would be loaded anyway.
If the program was not launched via ShellExecute, or the Path key was not defined, and the App Path option is selected, the AppPath directory will not be used.
In either case, it is necessary to re-launch the executable. This is accomplished by using the Re-Launch LoadLibrary Explorer button. This button is enabled whenever there is an inconsistency between the actual state and the desired state.
Note that after the launch, examining the PATH list shows the AppPath directory has been added. The list has been scrolled to the end. to show this.
Note from the above documentation that the SafeDllSearchMode key, HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode, the effect is to deemphasize the role of the working directory, pushing it lower on the search chain; this reduces the chances of a spoofing attack that replaces a system DLL.
To test this, it was necessary to launch a secondary program which had an implicit link to the target DLL. This program then notifies the launching process what DLL was loaded. This technique pointed out another issue. Although I illustrate the call as being ShellExecute, I actually use ShellExecuteEx, and specify the SEE_MASK_FLAG_NO_UI option, which states "Do not display an error message box if an error occurs". However, while one might be led to suspect that this is going to set the SetErrorMode state in the child process, it apparently has no effect. If there is a DLL load failure, the image below shows what happens.
Independent of the setting of the SafeDllSearchMode, launching a program where there is a copy of the DLL in both the System32 and the Working directory, the version from the Working directory is always loaded.
The above behavior is expected. The copy in the working directory is found first, because the dcoumented search path is
But when SafeDllSearchMode is set to 1, the result is
The correct target should have been the System32 directory, according to the documentation:
but that is not the behavior exhibited here.
Read the article in the MSDN titled "Dynamic-Link Library Redirection", which is found either by direct searching or under the topic
Win32 and COM Development
DLLs, Processes and Threads
DLLs, Processes and Threads
About Dynamic-Link Libraries
Dynamic-Link Library Redirection
The goal here is to demonstrate how to install what are called "side-by-side components" so your app will run with the appropriate version of a given DLL.
There are three options here
The documentation states that when redirection takes place via a .local file, the DLL that is desired is placed in the executable directory. No matter what path is actually specified in LoadLibrary(Ex) call, the system first checks to see if there is an application_name.local file in the executable directory. If there is, then the LoadLibrary call will ignore the specified path and load the DLL from the executable directory if it is found there; otherwise, the standard search mechanism will be used.
When rediretion takes place via a .local directory, a directory named application_name.local is created in the executable directory, and the DLL is installed in this directory. LoadLibrary(Ex) calls will ignore any explicit path and load the target DLL from that directory if it is found there, otherwise the standard search mechanism will be used.
Numerous experiments suggest that this method of redirection does not actually work in Windows XP SP2 or Server 2003! For example, see the screen snapshot below:
and just to verify that the directory actually exists, and the file exists within it:
As can be seen from either the log listing in the LoadLibrary Explorer, or from the command shell snapshot, the directory i:\mvp_tips\LoadLibraryExplorer\debug\LoadLibraryExplorer.exe.local definitely exists, and it definitely contains the specified target, "TheDLL.dll", but the load fails. All variants of attempts to load from this directory appear to fail. Yet I saw it work once, and have not been able to determine the confluence of circumstances that created that one-and-only event. Certainly everything I see in the example just illustrated suggest that there are problems here. Perhaps someone with more time than I have left right now can figure out what is happening (if so, send me email, and I'll add your information and give you a credit line!)
The EnumProcessModules button invokes the EnumProcessModules API and creates a list of all the modules. If the Alphabetical option is selected, they will be displayed in alphabetical order.
The module list is displayed in the log window.
Using the LoadLibrary Explorer, I have determined the following bugs appear in the Microsoft documentation:
Like most projects, this project illustrates a number of useful programming techniques.
This is a question which comes up frequently. "How can I use DLLs and distribute only a single executable?" Well, the real answer is that you should use an installer, but for this case I needed to have a file that was a "master copy" of the DLL but was not in fact the DLL. This was awkward to accomplish. For example, I could have had the linker write the file TheDLL.xxx, which I did originally, but when it came time to implement the SafeDllSearchMode, I needed to do implicit linking, and the name of the output file became part of the import record in the executable image. So I really had to write the output as TheDLL.dll, but this introduced problems when I wanted to move it around. This seemed a good opportunity to implement the DLL as a resource.
First, I had to create a dependency in the projects. The TheDLL project had to be built before the executable image that contains it. This is done by selecting Project > Project Dependencies... which brings up the dialog shown to the left. Then, as shown, the LoadLibraryExplorer project is made dependent on the TheDLL project.
The test application, SafeDllTest, is also made dependent on the build of TheDLL, because it requires the most recent .lib file be used to perform the linking of this application.
Next, I had to add the DLL as a resource. This was more than a little tricky, because I actually have two different DLLs I want to include. The Debug version of the project should include the Debug version of the DLL, and the Release version of the project should include the Release version of the DLL.
What I did was to first Import the DLL. Right-click on the project, select Add Resource, and the dialog shown to the right will appear. Click the Import... button. A standard file-open dialog will appear. What I did was go to the Debug directory, select TheDLL.dll, and click the Save option. This will then prompt for a resource type, which I gave the type "DLL". So now I was partway to my solution,
One thing I had to do was deal with the fact that the Import option, when pointed at the file, makes a copy of the file in the \res subdirectory of the project. I did not want the resource to come from the \res directory, I wanted it to always go out to the proper directory and get the most recent copy of the DLL. So I went to the Properties and edited the location. I also added a Condition property to the resource, indicating that it is included only if the _DEBUG symbol is defined.
Note the three key pieces of information in the properties shown. The Condition indicates the _DEBUG symbol is required to have this resource included. While it would have been nice to have been able to use a macro, such as $(OutDir), to indicate the source of the DLL, macros don't work here (makes you wonder sometimes about orthogonality), but I had to give a path which included the Debug mode.
Having redirected the source of the resource to its original directory, I had to go into the \res directory and delete the copy that had been made during the original import. Next, I added another resource, which was the version from the Release directory. In this case, I changed the Filename to point to the desired DLL, I added the conditional NDEBUG (which is defined in the non-debug build), and I also hand-edited the ID to be the same as the debug DLL's ID. The result was two different resources with the same ID, but which are included in the different builds. As with the first inclusion, I had to go back and delete the file from the \res subdirectory.
This got the resources into the executable. Now the problem was how to get them out. This was actually straightforward. To load the DLL resource, I simply called ::FindResource(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_DLL), _T("DLL")) to obtain an HRSRC handle. With this resource handle, I could call ::SizeofResource(AfxGetInstanceHandle(), rsrc) to find out the size. Then a LoadResource to get an HGLOBAL, and a LockResource to get a pointer to the data. Then all I had to do was a simple ::CreateFile of the name of the master copy of the DLL (which I called TheDLL.xxx), and I could do a ::WriteFile on the pointer returned by LockResource for the number of bytes obtained by SizeofResource, and the DLL prototype comes into existence. If you need only the DLL, then you could write the .dll file directly.
Another question that comes up frequently is how to communicate data structures between processes, the most common one being a string value. There are a number of possible ways to accomplish this, but WM_COPYDATA is a fairly straightforward way to handle this. This mechanism does have some risks, which I describe in an essay on how to use WM_COPYDATA. I did a slight variation on this class to support what I needed to implement this solution.
What I do is launch a child process which has an implicit DLL load to my DLL. I then use the techniques that allow the DLL to be placed anywhere in the set of possible targets, and I call an explicit function in the DLL to perform a ::GetModuleFileName. Now I need to communicate this information back to the parent. I could have done a printf, and captured the output via a named pipe, but the WM_COPYDATA solution gave me an excuse to write this section of the article.
The WM_COPYDATA message requires a destination window. I provide the window handle by passing in, as the first (and only) parameter of the invocation, the hexadecimal value of the window handle. Even though this is just a console application, the full Win32 API is available, and it can easily do a SendMessage to transmit the message to any window. Note that SendMessage is generally contraindicated as a means of inter-process (hence inter-thread) communication, but WM_COPYDATA is an exception: it can only be sent via SendMessage, not by the preferred PostMessage.
Much of what remains is very mundane, but I mention here for those who may be curious about how to do certain things:
Many of these techniques seem obvious once you've seen them, but may not be obvious until you've seen them at least once. But most of these techniques were simply "lying around" in my toolbox, easily copied and modified for this project. Even so, the project is a modest, under 10K source lines. Of those lines, I wrote somewhat less than 4000, and even many of those were written for me by the MFC ClassWizard and AppWizard. 3700 of those lines were in the single LoadLibraryExplorerDlg.cpp file, and the bulk of the remaining lines were in LoadLibraryExplorerDlg.h.
This project was written under VS.NET 2003 and may or may not work in VS6.
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.