It rather involved being on the other side of this airtight hatchway: Disabling Safe DLL searching


The Microsoft Vulnerability Research team discovered a potential current directory attack in a third party program. The vendor, however, turned around and forwarded the report to the Microsoft Security Response Center:

Our investigation suggests that this issue is due to a bug in Microsoft system DLLs rather than our program. When a process is launched, for example, when the user double-clicks the icon in Explorer, a new process object is created, and the DLLs are loaded by a component known as the Loader. The Loader locates the DLLs, maps them into memory, and then calls the DllMain function for each of the modules. It appears that some Microsoft DLLs obtain DLLs from the current directory and are therefore susceptible to a current directory attack. We created a simple Win32 application which demonstrates the issue:

#include <windows.h>

int __cdecl main(int argc, char **argv)
{
 return MessageBox(NULL, "Test", "Test", MB_OK);
}

If you place a fake copy of DWMAPI.DLL in the same directory as the application, then the Loader will use that fake copy instead of the system one.

This technique can be used to attack many popular programs. For example, placing a fake copy of DWMAPI.DLL in the C:\Program Files\Internet Explorer directory allows it to be injected into Internet Explorer. Placing the file in the C:\Program Files\Adobe\Reader 9.0\Reader directory allows it to be injected into Adobe Reader.

(I like how the report begins with some exposition.)

The vendor appears to have confused two directories, the current directory and the application directory. They start out talking about a current directory attack, but when the money sentence arrives, they talk about placing the rogue DLL "in the same directory as the application," which makes this not a current directory attack but an application directory attack.

We saw some time ago that the directory is the application bundle, and the application bundle can override DLLs in the system directory. Again, this is just another illustration of the importance of securing your application directory.

The specific attacks listed at the end of the report require writing into C:\Program Files, but in order to drop your rogue DWMAPI.DLL file into that directory, you need to have administrative privileges in the first place.

In other words, in order to attack the system, you first need to get on the other side of the airtight hatchway.

There was one final attempt to salvage this bogus vulnerability report:

We can also reproduce the problem without requiring write access to the Program Files directory by disabling Safe DLL searching.

Nice try. In order to disable Safe DLL searching, you need to have administrator privileges, so you're already on the other side of the airtight hatchway. And if you elevate to administrator and disable safe DLL searching, then is it any surprise that you have unsafe DLL searching? This is just another case of If you set up an insecure system, don't be surprised that there's a security vulnerability.

Comments (17)
  1. Ian says:

    Presumably the customer was simply wrong in their assertion that some Microsoft DLLs obtain their DLLs from the current directory – since the current directory comes after the application directory and the various system directories in the search order.

    Maybe they thought Microsoft were warning them of an 'application directory' vulnerability? It certainly looks like this is what they investigated, regardless of what Microsoft actually told them. I doubt it was deliberate; more likely incompetence or too many people in a game of Chinese whispers.

  2. Joshua says:

    > Chinese whispers

    Otherwise known as telephone.

  3. smf says:

    I'm more interested in the potential current directory attack that MVR discovered.

  4. Myria says:

    I wish the Windows kernel team would listen to your advice, Raymond. =)  On of the things I noticed about Windows 8.1 Preview is that the kernel team flagged csrss.exe as a Protected Process to try to prevent attacks on the kernel from programs running as LocalSystem.  Protecting the kernel from LocalSystem seems ridiculous from the "other side of this airtight hatchway" perspective.

  5. Joshua says:

    @Myria: If you ever ran code in CSRSS you know why it's protected. Besides the fact there are syscalls that don't have any argument protection other than caller = CSRSS (including clobbering kernel memory space).

  6. Hm says:

    The referenced MSDN entry Safe DLL Searching (msdn.microsoft.com/…/ms682586) is nearly unreadable. It contains eight somewhat different listings of search steps without proper differentiation when which of them applies.

    Nowhere is some explanation why the current directory is search at all, even when SafeDllSearchMode is enabled…

    Also, I have to assume that this fragment "If a DLL with the same module name is already loaded in memory, …" means "If a DLL with the same module name is already loaded into THE PROCESS, …". Is this correct?

  7. Pseudo-Anonymous says:

    If the application directory is the application bundle, then why do Windows Store apps for Windows 8.1 have .appxbundle package, or simply app bundle (msdn.microsoft.com/…/bg182885.aspx)?

    [You are confusing two unrelated concepts that happen to use the same name. I'm responding to "Why doesn't Windows have application bundles, like the Mac?" The application bundles I'm referring to in this article are Mac-style application bundles, which in Windows is simply "directories". The MSDN article is talking about an unrelated concept in Windows Store application packaging that happens to have chosen the same name. I'm actually surprised you found this confusing, but now that you point it out, I guess I'll have to change it to "the directory is the Windows equivalent of what on the Mac is called an application bundle" which is a lot more awkward. -Raymond]
  8. Anon says:

    @Hm

    Loading a new copy of the DLL every single time any program ever asks for it would be horribly inefficient, wouldn't you agree? You'd have to locate the DLL, allocate (and possibly page) memory, then load the DLL from disk. But there's already a copy available.

    There are obviously some security checks (to keep you from being stuck with \malwareservermalwaremalicious.dll when you wanted %SystemRoot%System32relativelylessmalicious.dll).

  9. Hm says:

    Anon, then how do you make sure that never ever some util.dll is loaded by some other process before your process tries to load its own util.dll with the same module name, but being totally unrelated in every other aspect? This makes no sense.

  10. Hm says:

    Anon, of course should the OS not allocate the same read-only pages twice, but the module name without path cannot be the criterion, as stated in that MSDN article. It must really be the exact same disk file.

  11. When an executable is loaded, it's opened for creating a file section. All processes that opened the same disk file will ultimately have the same cache pages mapped into their space, no matter what filename was used (it could be short filename or path components, or long, doesn't matter). As long as the file ID is the same (which is Windows equivalent of inode).

    But if you want a shared data section, that's where fun starts. The shared data section is named based on the full pathname of the DLL. Long and short names don't match. (also, it's login-session local).

  12. Hm says:

    @Alegr1, it would make sense if only pages backed up by the same dll file are shared across processes. But MSDN says "If a DLL with the same module name is already loaded in memory, the system checks only for redirection and a manifest before resolving to the loaded DLL, no matter which directory it is in. The system does not search for the DLL."

    This seems to indicate that the loader does not load the desired dll if some other ptocess has already loaded a dll with such name, even if this is not the file the application would load by its own regular search when it is the first process that tries to load this DLL ("no matter which directory it is in").

    If this is really done this way, I would call this very unsecure.

  13. "If a DLL with the same module name is already loaded in memory, the system checks only for redirection and a manifest before resolving to the loaded DLL, no matter which directory it is in. The system does not search for the DLL."

    This statement talks about DLLs of the same process. The loader doesn't have means to check what DLLs are loaded into other processes, and check their manifests.

  14. >it would make sense if only pages backed up by the same dll file are shared across processes.

    The pages backed by the same file ARE shared across processes, because this is how file mapping works. Some pages are mapped as copy-on-write, which makes a separate copy once the page is modified by the loader. Other pages are read-NX-only, or read-execute-only.

  15. Hm says:

    Alegr1, "ages backed by the same file ARE shared across processes". Yes, but this MSDN article claims that the loader reuses already-loaded DLLs ("already loaded in memory"), and therefore their code, before it has determined if this DLL is really the exact file requested by the application ("no matter which directory it is in").

    This seems incorrect behavior, or very bad documentation, or both, even if this is only done within the scope of a single process. How should I know if some shell extension (pulled-in by the File Open dialog) loads a DLL with a name which I'm also using for some of my own DLLs?

  16. Pseudo-Anonymous says:

    "You are confusing two unrelated concepts that happen to use the same name…"

    I wasn't paying attention when I read those articles.

  17. >How should I know if some shell extension (pulled-in by the File Open dialog) loads a DLL with a name which I'm also using for some of my own DLLs?

    Libraries loaded with two different full pathnames are loaded separately, even if their base filename is the same. Base filename matching is only done when only the filename is specified in LoadLibrary call.

    The shell extensions need to use full paths in LoadLibrary call. This will prevent ambiguity. In any case, for libraries other than RTL, you better use the full path, if you want to save trouble.

Comments are closed.