Holy cow, I wrote a book!
We saw last time
that your debugging code can be a security vulnerability
when you don't control the current directory.
A corollary to this is that your delayload code can also be
a security vulnerability, for the same reason.
When you use
the linker's delayload functionality
to defer loading a DLL until the first time it is called,
the linker injects code which calls LoadLibrary
on a DLL the first time you call a function in it,
and then calls GetProcAddress on the functions
When you call a delay-loaded function and the delayload code
did not get a function pointer from GetProcAddress
(either because the DLL got loaded but the function does not exist,
or because the DLL never got loaded in the first place),
it raises a special exception indicating that a delayed load failed.
Let's look again at the order in which the LoadLibrary
function searches for a library:
The code which implements the delayload functionality uses
a relative path when it passes the library name to LoadLibrary.
(It has no choice since all it has to work with is the library name
stored in the IMPLIB.)
Consequently, if the DLL you are delay-loading does not exist in
any of the first four search locations, the LoadLibrary
will look in location 5: the current directory.
At this point, the current directory attack becomes active,
and a bad guy can inject an attack DLL into your process.
this sample code
uses delayload to detect whether the functions in
dwmapi.dll exist, calling them if so.
If the function IsThemeEnabled is not available,
then it treats themes as not enabled.
If the program runs on a system without dwmapi.dll,
then the delayload will throw an exception,
and the exception is caught and turned into a failure.
But in fact, the disaster was not avoided; it was introduced.
If you run the program on a system without dwmapi.dll,
then a bad guy can put a rogue copy of dwmapi.dll into
the current directory,
and boom your process just loaded an untrusted DLL.
Using the delayload feature to probe for a DLL
is morally equivalent to using a plain LoadLibrary to probe
for the presence of a debugging DLL.
In both cases, you are looking for a DLL with the expectation that
there's a good chance it won't be there.
But it is exactly in those sometimes it won't be there
cases where you become vulnerable to attack.
If you want to probe for the existence of a DLL,
then you need to know what directory the DLL should
and then load that DLL via that full path
in order to avoid the current directory attack.
On the other hand, if the DLL you want to delayload is
known to be installed in a directory ahead of the current
directory in the search path
(for example, you require versions of the
the operating system in which the DLL is part of the mandatory install,
and the directory in which it is installed is the System32 directory)
then you can use delayload.
In other words, you can use delayload for delaying the load of a DLL.
But if you're using delayload to probe for a DLL's existence,
then you become vulnerable to a current directory attack.
This is one of those subtle unintended consequences of changing
the list of files included with an operating system.
If you take what used to be a mandatory component that can't
and you change it to an optional component that can be
then not only do programs which
linked to the DLL in the traditional manner stop loading,
but you've also introduced a security vulnerability:
Programs which had used delayload under the calculation
(correct at the time it was made) that doing so was safe
are now vulnerable to the current directory attack.