|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionAfter 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 ModeInternet 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 UIPIVista introduces a new attribute on securable objects called the mandatory integrity level. There are four levels:
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:
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. VirtualizationVirtualization (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 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 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 ExtensionThe 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 RestrictionsIE 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 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 RuntimeTo determine if our extension is running in a protected mode IE process, we use HRESULT IEIsProtectedModeProcess(BOOL* pbResult); If the return value is a successful HRESULT hr; BOOL bProtectedMode = FALSE; hr = IEIsProtectedModeProcess ( &bProtectedMode ); if ( SUCCEEDED(hr) && bProtectedMode ) // IE is running in protected mode else // IE isn't running in protected mode The sample band extension calls this API on startup, and shows a message indicating the status of protected mode. Writing to the File SystemWhen 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 When an extension wants to write to the file system, it should use the HRESULT IEGetWriteableFolderPath(GUID clsidFolderID, LPWSTR* lppwstrPath); The GUID is one of these, defined in 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);
// szTempFile now has the full path to the temp file.
}
If
Writing to the RegistrySince 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 HRESULT IEGetWriteableHKCU(HKEY* phKey); If it succeeds, you can use the returned Prompting the User to Save FilesWhen 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 The steps when saving a file are:
HRESULT IEShowSaveFileDialog( HWND hwnd, LPCWSTR lpwstrInitialFileName, LPCWSTR lpwstrInitialDir, LPCWSTR lpwstrFilter, LPCWSTR lpwstrDefExt, DWORD dwFilterIndex, DWORD dwFlags, LPWSTR* lppwstrDestinationFilePath, HANDLE* phState );
Here is the code in the demo project that saves the log to a file. We first call 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; // Get a filename from the user. 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 LPWSTR pwszCacheDir = NULL;
TCHAR szTempFile[MAX_PATH] = {0};
// Get the path to the IE cache dir, which is a dir that we're allowed
// to write to in protected mode.
hr = IEGetWriteableFolderPath ( FOLDERID_InternetCache, &pwszCacheDir );
if ( SUCCEEDED(hr) )
{
// Get a temp file name in that dir.
GetTempFileName ( CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile );
CoTaskMemFree ( pwszCacheDir );
// Write our data to that temp file.
hr = WriteLogFile ( szTempFile );
}
If everything has succeeded to far, we call another protected mode API, If for some reason we don't end up calling if ( SUCCEEDED(hr) ) { // If we wrote the file successfully, have IE save that data to // the path that the user chose. hr = IESaveFile ( hState, T2CW(szTempFile) ); // Clean up our temp file. DeleteFile ( szTempFile ); } else { // We couldn't complete the save operation, so cancel it. IECancelSaveFile ( hState ); } Enabling Communication Between Your Extension and Other ApplicationsAll 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 ObjectsWhen 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 // The LABEL_SECURITY_INFORMATION SDDL SACL to be set for low integrity 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
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 HANDLE hMapping;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
sa.bInheritHandle = TRUE;
hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa,
PAGE_READWRITE, 0, cbyData, NULL );
// Omitted: Put data in the shared memory block...
// Run the EXE and pass it the shared memory handle.
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, // TRUE => the new process should inherit handles
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );
DemoApp would then read the handle value from the 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: // Get a GUID that we'll use as the name of the shared memory object. GUID guid = {0}; WCHAR wszGuid[64] = {0}; HRESULT hr; CoCreateGuid( &guid ); StringFromGUID2( guid, wszGuid, _countof(wszGuid) ); // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES // struct since the handle won't be inherited. HANDLE hMapping; hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) ); // Omitted: Put data in the shared memory block... // Run the EXE and pass it the name of the shared memory object. // Note that the bInheritHandles param to CreateProcess() is FALSE. 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, // FALSE => the new process does not inherit handles 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
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.
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 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 MessagesUIPI prevents certain window messages (and all messages with a value greater than or equal to BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);
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 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 Other Restrictions in Protected ModeRunning Other ApplicationsIE 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
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 ApplicationsA 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 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 ReadingUnderstanding and Working in Protected Mode Internet Explorer IEBlog: Protected Mode in Vista IE7 Copyright and LicenseThis 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 HistoryMay 21, 2007: Article first published. | ||||||||||||||||||||