Wednesday, October 27, 2010

Breaking The SetDllDirectory Protection Against Binary Planting

The curious case of Windows environment variables or how to re-hack fixed iTunes and Safari

If you're a Windows developer trying to protect your applications from binary planting attacks, you have probably heard of the SetDllDirectory function. This function removes the current working directory from the search path when  loading DLLs and allows you to replace it with a (hopefully safe) location you specify.

For instance, the DLL search order after calling SetDllDirectory("C:\SafePath\") becomes:
  1. The directory from which the application loaded
  2. C:\SafePath\ 
  3. 32-bit System directory (Windows\System32)
  4. 16-bit System directory (Windows\System)
  5. Windows directory (Windows)
  6. Directories in the PATH environment variable

When providing an empty string to SetDllDirectory, the current working directory is simply removed from the DLL search order:
  1. The directory from which the application loaded
  2. 32-bit System directory (Windows\System32)
  3. 16-bit System directory (Windows\System)
  4. Windows directory (Windows)
  5. Directories in the PATH environment variable

In theory, calling SetDllDirectory at the beginning of your code should solve the binary planting problem at least as far as loading DLLs go (reminder: EXEs are also affected). The concept sounds good and it is in fact one of the recommendations in our developer guidelines for preventing binary planting vulnerabilities.

In practice though, DLL-based binary planting attacks may still be possible. How? Enter the world of unexpanded environment variables, a known Windows bug that goes a long way in helping binary planting attackers.

Our researchers have (re)discovered a curious fact that some Windows environment variables sometimes don't get expanded in the PATH. For example, the CommonProgramFiles environment variable, which typically expands to "C:\Program Files\Common Files", sometimes simply remains %CommonProgramFiles% in the PATH. This bug affects many other environment variables on all Windows platforms and has not only been publicly discussed before since 2003 (see here, here, here, here and here) but Microsoft has also issued a KB article on the subject 3 years ago. The bug, however, was never fixed.

Now what does this mean for binary planting? Suppose an application calls SetDllDirectory to protect itself against DLL planting attacks. Obviously, the current working directory is no longer in its library search path - but the PATH still is! And if any one of the environment variables in the PATH remains unexpanded, well..., it becomes a relative path. That is, relative to the current working directory. For instance, if the PATH looks like this:

PATH=C:\Program Files\Java\jdk1.6.0_21\bin;
%CommonProgramFiles%\Microsoft Shared\Windows Live;
C:\Program Files\QuickTime\QTSystem\

and the CommonProgramFiles variable doesn't get expanded, the actual PATH included in the search path will contain the loaction

%CommonProgramFiles%\Microsoft Shared\Windows Live

In case the DLL hasn't been found in preceding locations in the search path, the application will at this point try to load it from a subdirectory of the current working directory, specifically from

CWD\%CommonProgramFiles%\Microsoft Shared\Windows Live

For illustration purposes let's take Apple's latest iTunes and Safari as examples (remember that their DLL and EXE planting bugs have already been fixed). These applications both call SetDllDirectory at the beginning of their execution, which is a secure coding practice that we recommend. Now let's assume you also have ActivePython installed. ActivePython adds %APPDATA%\Python\Scripts to the system PATH. There is nothing wrong with this! What is wrong is that APPDATA, due to the Windows bug described above, often remains unexpanded in PATH, which effectively makes iTunes, Safari and countless other applications vulnerable to binary planting attacks.

Let's see this in action.

Again, this is not Apple's fault (although they could improve their code by specifying full paths to their libraries) and it's certainly not ActivePython's fault (PATH is meant to be used this way).

Besides, it's not just the APPDATA variable that helps attackers in this way. Our Online binary planting exposure test's logs show Windows machines trying to load DLLs from various unexpected paths, including:

  • %APPDATA%/Python/Scripts
  • %ProgramFiles(x86)%
  • %CommonProgramFiles%/Microsoft Shared/Windows Live
  • %PROGRAMFILES(x86)%/Common Files/Microsoft Shared/Ink
  • %USERPROFILE%/Local Settings/Temp
  • %systemroot%/system32/DATA/Config
  • %NpmLib%

What can we learn from this all? Mostly, don't just depend on SetDllDirectory to solve your DLL-planting problems. Use absolute, fully qualified paths to DLLs when loading them. Until Microsoft fixes this bug, any application that sets user or system PATH can unwittingly make your application vulnerable to binary planting if you're loading libraries from relative paths. And even then, some particularly adventurous application might add a relative location to PATH for whatever peculiar purpose, and break the SetDllDirectory  protection for the entire system. Again, use absolute paths to DLLs!

Finally, it needs to be said that this bug fortunately doesn't hinder the effectiveness of Microsoft's CWDIllegalInDllSearch hotfix. This hotfix successfully blocks DLL loads from the current working directory if configured properly even if relative locations are found in the PATH (it does nothing to prevent EXE planting attacks though).

Unexpanded variables bug analysis

For those interested in more information about the curious behavior of this Windows bug, here are some of our most important observations. We were testing on Windows XP Professional 32 bit, Windows Vista Business 32 and 64 bit and Windows 7 Professional 32 bit.

  1.  We noticed that the "expandability" of certain environment variables sometimes changes by logging off or restarting the computer. For instance, APPDATA in the user PATH is initially unexpanded on Windows 7, but gets expanded after logoff or restart. On Windows XP, however, logoff makes this variable "expandable," but restarting the machine again makes it "nonexpandable".
  2. Windows Vista 64 bit never expanded APPDATA in the user PATH, whatever we tried.
  3. While Windows XP expanded APPDATA in system PATH at least after logoff, Windows Vista and Windows 7 never expanded APPDATA in system PATH.
  4. COMMONPROGRAMFILES is never expanded in system PATH upon initial setting, and while other systems resolve this after logoff or restart, Windows Vista 64 bit never seems to expands this variable, period.
  5. The same goes for PROGRAMFILES, which is a very common variable to use.
Many other environment variables are affected, making it relatively likely that the SetDllDirectory protection against binary planting will not be efficient on computers with multiple software products installed (which might add locations to user or system PATHs).