Contents
Introduction
After seeing Vista and IE 7 at PDC 2005, I remarked
that protected mode was going to make life difficult for legitimate IE extensions, not just spyware. Last year
while trying out Vista beta 2, I saw just how difficult - one of my products has an IE toolbar, and protected mode
broke many of the major features because the toolbar needs to communicate with another process outside of IE. After
a lot of trial and error, and some not-kid-sister-safe language, I eventually got the features working again. After
all that work, I wanted to summarize the necessary changes into this survival guide and give folks all the relevant
information in one place.
This article assumes you are familiar with C++, GUI, and COM programming. The sample code is built with Visual
Studio 2005, WTL 7.5, and the
Windows SDK. If you need to get up to speed with WTL, check out my series of articles
on WTL. WTL is only used for the GUI; the concepts presented here apply to all applications, and are independent
of any class library.
Introduction to Protected Mode
Internet Explorer's Protected Mode is a new feature in Vista, and is one of the pieces of User Account
Control (UAC). Protected mode is designed to protect the computer by restricting the parts of the system that code
running in the IE process can affect. If a malicious web page exploits a code-injection bug in IE or an IE plugin,
that code will not be able to do damage to the system.
Before we dive into what protected mode means for IE plugin developers, we need a quick tour of the security
features involved.
Integrity Levels and UIPI
Vista introduces a new attribute on securable objects called the mandatory integrity level. There are
four levels:
- System: Used by OS components, should not be used by applications.
- High: Processes that are running elevated with full admin rights.
- Medium: Processes launched in the normal fashion.
- Low: Used by IE and Windows Mail to provide protected mode.
The information that Windows keeps about a process includes which integrity level it was launched with. This
level can never change once the process is started, it can only be set at the time the process is created. A process's
integrity level has three main effects:
- Any securable objects that the process creates get that same integrity level.
- The process cannot access a resource whose integrity level is higher than the process's own level.
- The process cannot send window messages to a process that has a higher integrity level.
That is not a complete list, but the three listed above have the greatest impact on plugins. The first two items
prevent a low-integrity process from tampering with IPC resources, like shared memory, that contain sensitive data
or data that is crucial for the proper operation of an application. The last item is called User Interface Process
Isolation (UIPI), and is designed to prevent attacks like the shatter
attack, where an attacker induces a process into running untrusted code by sending it messages that it was
not expecting to receive.
Virtualization
Virtualization (also called redirection in some Microsoft documentation) is a feature that prevents
a process from writing to protected areas of the registry and file system, while still allowing the application
to function normally. For medium-integrity processes, the protected areas are system-critical areas like HKLM
,
the system32
and Program Files
directories, and so on. A low-integrity process is even
more restricted -- it can only write to special low-rights areas of the registry and file system, and any
attempts to write outside those areas are prevented.
When a process tries to write to an area it doesn't have rights to, virtualization kicks in and redirects the
write operation to a directory (or registry key) under the current user's profile, and the write operation actually
happens there. Then later, when the app tries to read that data, the read operation is also redirected so the app
will see the data that it wrote earlier.
Virtualization affects IE extensions because they can no longer do things like write settings to the registry
(even under HKCU
) for other processes to use. Extensions are also very restricted in where they can
write data files -- only a few IE-specific directories like Favorites
and Cookies
are writable.
When Is Protected Mode Turned On?
In Vista's default configuration, IE always runs in protected mode. The status bar, pictured below, has an indicator
that shows when protected mode is on:
You can turn protected mode off entirely by disabling UAC, or by unchecking Enabled Protected Mode on
the Security tab of the IE options dialog. You can also temporarily bypass protected mode by running a new
elevated instance of IE, but keep in mind that doing so will make IE run at high integrity, not at medium like
a normal application.
The Sample App and Extension
The sample code for this article combines two projects. The first project, IEExtension, is a band that
is docked to the bottom of the IE window:
The second project, DemoApp, is an EXE that the band communicates with. DemoApp doesn't do much by itself,
the interesting parts are in IEExtension where it needs to communicate with the EXE. This communication is greatly
impacted by protected mode, and both projects demonstrate how to work within protected mode's restrictions and
still be able to do inter-process communication.
The band has several buttons for doing various IPC tasks. The buttons that are in pairs show both an old technique
that no longer works in protected mode (button 1) and a new protected mode-aware technique that does work (button
2). The list control shows various status messages, such as the return value from Windows APIs.
The rest of the article will focus on what the extension needs to do to function properly in protected mode.
I'll be introducing some APIs, then showing the code in the sample extension that uses that API. Each topic corresponds
to a button (or pair of buttons) in the band, so you can refer to the corresponding code while reading through
the article.
Working Within Protected Mode Restrictions
IE 7 has several new APIs that extensions can use to perform functions that are restricted in protected mode.
These APIs are in ieframe.dll. You can either link directly to these APIs by using the iepmapi.lib
import library, or use LoadLibrary()
/GetProcAddress()
to get pointers to the functions
at runtime. The latter method is the one you must use if you want your extension to load on versions of Windows
prior to Vista.
Many of the features that perform privileged actions use a broker process, ieuser.exe. Since the IE process
is running at low integrity, it can't do higher-privileged tasks on its own; ieuser.exe fulfills that role. You'll
see this broker process mentioned often in this article and in Microsoft documentation.
Detecting Protected Mode at Runtime
To determine if our extension is running in a protected mode IE process, we use IEIsProtectedModeProcess()
:
HRESULT IEIsProtectedModeProcess(BOOL* pbResult);
If the return value is a successful HRESULT
and *pbResult
is TRUE
, then
protected mode is enabled. Based on the value returned in *pbResult
, you can take different action
in your code if necessary:
HRESULT hr;
BOOL bProtectedMode = FALSE;
hr = IEIsProtectedModeProcess ( &bProtectedMode );
if ( SUCCEEDED(hr) && bProtectedMode )
else
The sample band extension calls this API on startup, and shows a message indicating the status of protected
mode.
Writing to the File System
When protected mode is enabled, an extension can only write to a few directories under the user's profile. There
are special low-integrity directories under the Temp
, Temporary Internet Files
, Cookies
,
and Favorites
directories that are writable. IE also has some compatibility shims, which virtualize
other commonly-used directories. (I haven't seen a full list of those "common directories." This
post on the IEBlog mentions the shim feature, but doesn't elaborate on which directories are covered.) Write
operations to those directories will be redirected to a subdirectory of Temporary Internet Files
.
If an extension tries to write to a sensitive location, like the windows
directory, the operation
will fail.
When an extension wants to write to the file system, it should use the IEGetWriteableFolderPath()
API instead of GetSpecialFolderPath()
, GetFolderPath()
, or SHGetKnownFolderPath()
.
IEGetWriteableFolderPath()
is aware of protected mode, and if the extension asks for a directory that
it is not allowed to write to, IEGetWriteableFolderPath()
will return E_ACCESSDENIED
.
The prototype for IEGetWriteableFolderPath()
is:
HRESULT IEGetWriteableFolderPath(GUID clsidFolderID, LPWSTR* lppwstrPath);
The GUID is one of these, defined in knownfolders.h
: FOLDERID_InternetCache
, FOLDERID_Cookies
,
FOLDERID_History
. There doesn't seem to be a GUID for the Temp
directory, so I recommend
using FOLDERID_InternetCache
when you need to write temp files.
Here's a snippet that creates a temp file in the cache:
HRESULT hr;
LPWSTR pwszCacheDir = NULL;
TCHAR szTempFile[MAX_PATH] = {0};
hr = IEGetWriteableFolderPath(FOLDERID_InternetCache, &pwszCacheDir);
if ( SUCCEEDED(hr) )
{
GetTempFileName(CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile);
CoTaskMemFree(pwszCacheDir);
}
If IEGetWriteableFolderPath()
succeeds, it allocates a buffer and returns its address in pwszCacheDir
.
We pass that directory to GetTempFileName()
, then free the buffer with CoTaskMemFree()
.
IEGetWriteableFolderPath()
is not just for writing temp files. An extension will also use it when
it uses the protected mode version of the save file dialog, as explained in the Prompting the User to Save Files
section below. The demo project uses this API when you click the Save Log button.
Writing to the Registry
Since the registry is a critical part of the system, it's crucial that code running in the browser not be allowed
to change any parts of the registry that could allow malicious code to run. To this end, there is only one
key that extensions can write to. As with the file system, this key is in a special low-rights area under the current
user's profile. To get a handle to this key, call IEGetWriteableHKCU()
:
HRESULT IEGetWriteableHKCU(HKEY* phKey);
If it succeeds, you can use the returned HKEY
in other registry APIs to write whatever data is
necessary. The demo project does not use the registry, but this API is quite simple and there should be no trouble
in using it.
Prompting the User to Save Files
When IE is running in protected mode, there is still a way for extensions to (indirectly) write to the file
system, outside of the low-rights areas. An extension can show a common save file dialog by calling IEShowSaveFileDialog()
.
If the user enters a file name, the extension can then have IE write the file by calling IESaveFile()
.
Note that this operation always results in the user seeing the save file dialog; this ensures that the user is
always aware that a file is about to be written.
The steps when saving a file are:
- Call
IEShowSaveFileDialog()
to show the save file dialog.
- Call
IEGetWriteableFolderPath()
to get the IE cache directory.
- Write the data to a temp file in the cache directory.
- Call
IESaveFile()
to copy that data to the filename that the user selected.
- Clean up the temp file.
IEShowSaveFileDialog()
is a wrapper around the common file save dialog:
HRESULT IEShowSaveFileDialog(
HWND hwnd,
LPCWSTR lpwstrInitialFileName,
LPCWSTR lpwstrInitialDir,
LPCWSTR lpwstrFilter,
LPCWSTR lpwstrDefExt,
DWORD dwFilterIndex,
DWORD dwFlags,
LPWSTR* lppwstrDestinationFilePath,
HANDLE* phState
);
hwnd
is a window owned by the extension, IE will use the topmost owner window as the parent window
of the dialog. lppwstrDestinationFilePath
is a pointer to an LPWSTR
that is set to the
file path that the user selects. This is only informational, since the extension can't write to that path directly.
phState
is a pointer to a HANDLE
that is filled in if the user chooses a file, this handle
is used when calling other APIs. The other parameters are used like the corresponding members in the OPENFILENAME
struct.
IEShowSaveFileDialog()
returns S_OK
if the user chooses a filename, S_FALSE
if he cancels the dialog, or a failure HRESULT
if the API fails.
Here is the code in the demo project that saves the log to a file. We first call IEShowSaveFileDialog()
to prompt the user for a file path:
void CBandDialog::OnSaveLog(UINT uCode, int nID, HWND hwndCtrl)
{
HRESULT hr;
HANDLE hState;
LPWSTR pwszSelectedFilename = NULL;
const DWORD dwSaveFlags =
OFN_ENABLESIZING | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST |
OFN_OVERWRITEPROMPT;
hr = IEShowSaveFileDialog (
m_hWnd, L"Saved log.txt", NULL,
L"Text files|*.txt|All files|*.*|",
L"txt", 1, dwSaveFlags, &pwszSelectedFilename,
&hState );
if ( S_OK != hr )
return;
Next, we use IEGetWriteableFolderPath()
to get the location of the cache directory that we can
write to.
LPWSTR pwszCacheDir = NULL;
TCHAR szTempFile[MAX_PATH] = {0};
hr = IEGetWriteableFolderPath ( FOLDERID_InternetCache, &pwszCacheDir );
if ( SUCCEEDED(hr) )
{
GetTempFileName ( CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile );
CoTaskMemFree ( pwszCacheDir );
hr = WriteLogFile ( szTempFile );
}
If everything has succeeded to far, we call another protected mode API, IESaveFile()
. IESaveFile()
takes the state handle that IEShowSaveFileDialog()
returned, and the path to our temp file. Note that
this HANDLE
isn't a standard handle and doesn't need to be closed; after the IESaveFile()
call, the HANDLE
is automatically freed.
If for some reason we don't end up calling IESaveFile()
-- for example, if an error happens
while writing to the temp file -- we do need to clean up the HANDLE
and any internal data
that IEShowSaveFileDialog()
allocated. We do this by calling IECancelSaveFile()
.
if ( SUCCEEDED(hr) )
{
hr = IESaveFile ( hState, T2CW(szTempFile) );
DeleteFile ( szTempFile );
}
else
{
IECancelSaveFile ( hState );
}
Enabling Communication Between Your Extension and Other Applications
All the file system and registry topics we've seen so far have been dealing with code running in the IE process.
With the availability of virtualization and compatibility shims, it is straightforward for IE to restrict code
running in its own process and prevent it from making calls to APIs that could end up damaging the system. Now
we'll see a more complex subject, which has a more complex solution: IPC with another process running at a higher
integrity level. We'll cover two different forms of IPC: kernel objects and window messages.
Creating IPC Objects
When an extension and a separate process want to communicate, that communication happens between the two pieces
of code, without going through IE wrappers. The NT security APIs and the mandatory integrity level come into play,
and by default communication from the extension to the separate app is blocked because the app is running at a
higher integrity level than IE.
If the separate app creates a kernel object (for example, an event or mutex) that the extension needs to use,
the app must lower the integrity level of the object before the extension can access it. The app can use the security
APIs to change the ACL on the object and lower its integrity. The code below, taken from the MSDN article "Understanding
and Working in Protected Mode Internet Explorer", takes a HANDLE
to a kernel object and sets
its integrity level to low.
LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)";
bool SetObjectToLowIntegrity(
HANDLE hObject, SE_OBJECT_TYPE type = SE_KERNEL_OBJECT)
{
bool bRet = false;
DWORD dwErr = ERROR_SUCCESS;
PSECURITY_DESCRIPTOR pSD = NULL;
PACL pSacl = NULL;
BOOL fSaclPresent = FALSE;
BOOL fSaclDefaulted = FALSE;
if ( ConvertStringSecurityDescriptorToSecurityDescriptorW (
LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) )
{
if ( GetSecurityDescriptorSacl (
pSD, &fSaclPresent, &pSacl, &fSaclDefaulted ) )
{
dwErr = SetSecurityInfo (
hObject, type, LABEL_SECURITY_INFORMATION,
NULL, NULL, NULL, pSacl );
bRet = (ERROR_SUCCESS == dwErr);
}
LocalFree ( pSD );
}
return bRet;
}
The sample code uses two mutexes whose purpose is to allow the extension to tell when the app is running. The
DemoApp EXE creates them when the process starts, and the extension tries to open them when you click one of the
Open Mutex buttons. Mutex 1 has the default integrity level, while mutex 2 is set to low integrity using
the SetObjectToLowIntegrity()
function listed above. This means that when protected mode is on, the
extension will only be able to access mutex 2. Here are the results you'll see when you click both of the Open
Mutex buttons:
Another effect of protected mode is that an extension can't have a separate app inherit a kernel object handle.
For example, when protected mode is enabled, our extension can't create a file mapping object, run the separate
app (passing TRUE
for the bInheritHandles
parameter to CreateProcess()
),
and have the app inherit the handle on the file mapping object.
HANDLE hMapping;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
sa.bInheritHandle = TRUE;
hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa,
PAGE_READWRITE, 0, cbyData, NULL );
CString sCommandLine;
BOOL bSuccess;
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = {0};
sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"),
hMapping );
bSuccess = CreateProcess(
NULL, sCommandLine.GetBuffer(0), NULL, NULL,
TRUE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );
DemoApp would then read the handle value from the /h
switch, and use that value to call MapViewOfFile()
and read the data. This is a standard technique to make the new process automatically receive a handle to a kernel
object, but when protected mode is enabled, the new process is actually launched by the broker process. Since the
IE process doesn't directly launch the new process, handle inheritance doesn't work.
To work around this restriction, the extension can use a pre-defined name for an IPC object, and the separate
app will be able to access that object since the object will have low integrity. If you don't want to use a pre-defined
name, you can generate a name at runtime (for example, use a GUID for the name) and pass that name to the separate
app:
GUID guid = {0};
WCHAR wszGuid[64] = {0};
HRESULT hr;
CoCreateGuid( &guid );
StringFromGUID2( guid, wszGuid, _countof(wszGuid) );
HANDLE hMapping;
hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) );
CString sCommandLine;
BOOL bSuccess;
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = {0};
sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"),
wszGuid );
bSuccess = CreateProcess(
NULL, sCommandLine.GetBuffer(0), NULL, NULL,
FALSE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );
Using this method, the EXE receives the IPC object's name on the command line. It can then call OpenFileMapping()
to access the object. There is one complication with this method, however, in that you need to be careful about
object lifetime management. When using handle inheritance, the sequence of events is:
- The extension creates the IPC object, giving it a reference count of 1.
- The extension launches the new process, which inherits the handle. This increases the object's reference count
to 2.
- The extension can immediately close its handle since it doesn't need to the object anymore. The reference count
drops to 1.
- The new process does whatever it needs to with the IPC object. Since it still has an open handle, the object
remains around until the new process closes its handle.
If we used the above steps when passing just the name of the object to the EXE, we would create a race condition
where the extension could close its handle (and therefore delete the IPC object) before the EXE has a chance to
open a handle on it.
- The extension creates the IPC object, giving it a reference count of 1.
- The extension launches the new process, passing it the name of the IPC object. The reference count is
still 1.
- The extension cannot close its handle right away, it needs to wait until the new process has opened a handle
to the object. Some sort of synchronization is necessary to do this.
- The new process opens a handle to the object and reads the data. At this point, it can signal the extension
to wake the extension's thread. It's now safe for the extension to close its handle.
What I chose to do in the sample project is have DemoApp read the data from shared memory before it creates
the main dialog. The extension then calls WaitForInputIdle()
after CreateProcess()
, which
makes the thread block until DemoApp's main dialog has been created and displayed. Once the DemoApp's thread goes
idle, it has finished using the shared memory, and it's safe for the extension to close its handle.
The band demonstrates both methods of passing data through shared memory. When you click the Run EXE 1
button, the band writes the current date and time to shared memory, and passes a handle to DemoApp. When protected
mode is enabled, this method will fail and DemoApp will report and invalid handle error. Clicking Run EXE 2
passes the name of the file mapping object to DemoApp, which will then show the data it reads from shared memory:
Accepting Window Messages
UIPI prevents certain window messages (and all messages with a value greater than or equal to WM_USER
)
from being sent from a lower-integrity process to a higher-integrity one. If your application needs to receive
messages from your extension, you can call ChangeWindowMessageFilter()
to allow one specific message
through:
BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);
message
is the value of the message, and dwFlag
indicates whether the message should
be allowed or blocked. Pass MSGFLT_ADD
to allow the message, or MSGFLT_REMOVE
to block
it. Be very careful when processing window messages from other processes - if you accept data via a message, you
must treat it as untrusted and you should validate it before acting on it. (Remember that inter-process messages
can be sent from anywhere, and such messages can be used for attacks, as mentioned earlier.)
The demo project shows how to communicate via registered window messages. As with the mutex example, there are
two messages. DemoApp allows the second message through the filter with this code in OnInitDialog()
:
m_uRegisteredMsg1 = RegisterWindowMessage( REGISTERED_MSG1_NAME );
m_uRegisteredMsg2 = RegisterWindowMessage( REGISTERED_MSG2_NAME );
ChangeWindowMessageFilter( m_uRegisteredMsg2, MSGFLT_ADD );
When you click the two Send Message buttons in the band, you'll see these results:
The first message is not allowed through the filter, and SendMessage()
returns 0.
Other Restrictions in Protected Mode
Running Other Applications
IE has another mechanism that prevents malicious code from communicating with or launching other processes.
If an extension tries to start another process, IE will ask the user for permission before starting the process.
For example, using the View Source command results in this prompt:
If your extension needs to run a separate EXE, you can add a registry key that tells IE that your EXE is trusted
and can be run without a prompt. The registry key that controls this behavior is HKLM\Software\Microsoft\Internet
Explorer\Low Rights\ElevationPolicy
. Create a new GUID, then add a key under ElevationPolicy
whose name is that GUID. In that new key, create three values:
AppName
: The filename of the executable, for example "DempApp.exe".
AppPath
: The directory where the EXE is located.
Policy
: A DWORD
set to 3.
If your installer doesn't create a key like that, IE itself will create one if you check the Do not show
me the warning for this program again checkbox.
Drag and Drop to Other Applications
A similar prompt will be shown if you try to drag content from a web page and drop it in another app:
This prompt can be suppressed with a registry key as well. The format is the same as described above, but your
app's key goes under DragDrop
instead of ElevationPolicy
.
DemoApp registers as a drop target, and if you select text in IE and drag it into the DemoApp dialog, it will
show a message indicating that it received the drag:
Further Reading
Understanding and Working
in Protected Mode Internet Explorer
IEBlog: Protected Mode in Vista IE7
Copyright and License
This article is copyrighted material, ©2007 by Michael Dunn, all rights reserved. I realize this isn't
going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in
doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission
to do a translation, I would just like to be aware of the translation so I can post a link to it here.
The demo code that accompanies this article is released to the public domain. I release it this way so that
the code can benefit everyone. (I don't make the article itself public domain because having the article available
only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own
application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are
benefitting from my code) but is not required. Attribution in your own source code is also appreciated but not
required.
Revision History
May 21, 2007: Article first published.
May 24, 2007: Added License section. Fixed IEExtension project in the sample code, it was missing the IDL file.