Introduction
While Internet Explorer has nice extensibility support, it does not go as far as Firefox. One of the easy tasks to do when you build Firefox extensions is to add custom buttons next to the address bar, but it is officially impossible to do that in Internet Explorer. This could be a problem if we want to preserve the same user interface for our extensions across different browsers.
A solution is possible but it relies heavily on undocumented structures and the behavior of Internet Explorer. This article will show how dirty and convoluted this task can get.
You should be familiar with programming BHOs (Browser Helper Objects) in order to follow the article since the basics about BHOs are not covered here.
Sneaking into Internet Explorer's Address Bar
Finding the Address Bar Window
The first this we should do is to explore the window structure of Internet Explorer. Microsoft Spy++ is the perfect tool for the job. To locate the toolbar window, just use the Window Search tool Spy++ (Search -> Find Window...) and drag and drop on the toolbar next to the address edit box. Notice that the "Search" button (magnifying glass) is not part of the toolbar but the address combo box.
After we select the toolbar window, Spy++ reveals to us this window hierarchy:
As we can see, two of the windows are interesting to us:
- The first one receives and handles
WM_COMMAND
messages sent from the toolbar when the user clicks the button. Its class name is "Address Band Root". - The second is the toolbar itself, which we want to manipulate. Its class name is "ToolbarWindow32".
Finding these windows is done by the FindAddressBar
function in barlib.cpp:
BOOL FindAddressBar(
HWND mainWnd, HWND* addressBarWnd, HWND* cmdTargetWnd)
{
mainWnd = ::FindWindowEx( mainWnd, NULL, TEXT( "WorkerW" ), NULL );
mainWnd = ::FindWindowEx( mainWnd, NULL, TEXT( "ReBarWindow32" ), NULL );
*cmdTargetWnd = ::FindWindowEx
mainWnd, NULL, TEXT( "Address Band Root" ), NULL );
if( *cmdTargetWnd )
*addressBarWnd = ::FindWindowEx(
*cmdTargetWnd, NULL, TEXT( "ToolbarWindow32" ), L"Page Control" );
return cmdTargetWnd != NULL;
}
This function takes the handle to the main browser window and stores the handles to the located windows. It returns FALSE
if it is unable to locate the required windows. This procedure works only with Internet Explorer 7 or higher. Previous versions of Internet Explorer have a similar window structure but not entirely the same, so this function is not compatible.
Finding the browser's main window is a fairly trivial task. We just need to invoke get_HWND
on the browser object:
HWND parent ;
_webBrowser2->get_HWND( (SHANDLE_PTR*)&parent );
HWND addressBarWnd, cmdTargetWnd;
if( ::FindAddressBar( parent, &addressBarWnd, &cmdTargetWnd ) )
_proxy = new CAddressBarAccessProxy(
g_hInst, addressBarWnd, cmdTargetWnd );
Window Subclassing
Now that we have the handle to the toolbar, we can manipulate it like a standard one, but we also need to subclass it if we want fine control over it. To do this, we just need to replace the function that handles the messages directed to the window with our own, but we need to store the old function so it can handle messages that we are not interested in handling.
The GetWindowLongPtr
API call with GWLP_WNDPROC
as the second parameter is used to obtain the current function that handles the messages. The GetWindowLongPtr
call sets the new message handler. Our function has to call the old one for all messages that we do not want to handle. To do so, we need to invoke CallWindowProc
and pass it a pointer to the old function which we replaced along with other information about the received message.
It is not enough to subclass just the toolbar window, because WM_COMMAND
generated when the user clicks on a button are sent to another window. To be able to handle the user clicks on newly added buttons, we need to subclass the toolbar's parent too.
typedef LRESULT (CALLBACK *WndProcPtr)(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
WndProcPtr oldAddrBarWndProc = NULL;
WndProcPtr oldCmdTargetWndProc = NULL;
void SubclassAddressBar(HWND addressBarWnd, HWND cmdTargetWnd)
{
oldAddrBarWndProc = (WndProcPtr)::GetWindowLongPtr(
addressBarWnd, GWLP_WNDPROC );
::SetWindowLongPtr(
addressBarWnd, GWLP_WNDPROC, (LONG_PTR)AddrBarWndProcS );
oldCmdTargetWndProc = (WndProcPtr)::GetWindowLongPtr(
_cmdTargetWnd, GWLP_WNDPROC );
::SetWindowLongPtr(
_cmdTargetWnd, GWLP_WNDPROC, (LONG_PTR)CmdTargetWndProcS );
}
LRESULT CAddressBarAccessServer::AddrBarWndProcS(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc( _instance->_oldAddrBarWndProc,
hwnd, uMsg, wParam, lParam );
}
LRESULT CmdTargetWndProcS(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc( oldCmdTargetWndProc,
hwnd, uMsg, wParam, lParam );
}
Internet Explorer 8+ with Protected Mode Enabled
Internet Explorer 8 introduced the Protected mode, and one of the consequences is that each tab has its own process which is separated from the process that hosts the browser's main window (whose part is the address bar). This is the first bad news for us. Our BHOs are hosted within the tab process for each one, meaning we have to cross process boundaries in order to get to the browser's tool bar. This is where (cue the music) DLL injection comes into play.
Now, a straightforward implementation would be for our BHO to inject another DLL into the browser's main process, but as it turns out, it's not that simple. The second bad news is the thing called integrity level which each process has on newer versions of Windows (Vista and 7). There are several possible levels, which we will not discuss here, but the main principle is that a lower integrity process cannot interfere with a higher integrity process. Internet Explorer spawns tab processes at the lowest integrity level while the process that hosts the main window runs at medium level.
Fortunately, this is where the good news start coming. When the tab process spawns a new process, it is created at medium level - the same integrity level as the process we are trying to reach. So to make the DLL injection to work, we just need to create a broker process whose sole purpose is to inject the DLL in the main process of the browser. All good and well, but starting higher level processes than the level at which the parent process is running while in Protected mode will show the user a prompt asking whether it should proceed:
It is possible to suppress this prompt by creating an elevation policy for the broker process. Elevation policy is a group of Registry keys and values located at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy. Creating a policy is done in the registration script of our BHO, and it will be described later.
Implementation
All code responsible for the browser's toolbar manipulation is isolated in a separate static library named ieb_lib. This library is part of the BHO's DLL. It is also linked with the auxiliary DLL which represents the wrapper so it can be injected into the browser's main process.
ieb_start.exe represents the broker process that injects the wrapper DLL into the browser's main process.
DLL Injection
Injection starts by spawning the broker process which performs the actual injection:
BOOL InjectDll(HINSTANCE bhoDll, DWORD procID)
{
STARTUPINFO startInfo;
::ZeroMemory( &startInfo, sizeof( startInfo ) );
startInfo.cb = sizeof( startInfo );
startInfo.dwFlags |= STARTF_USESHOWWINDOW;
startInfo.wShowWindow = FALSE;
PROCESS_INFORMATION processInfo;
::ZeroMemory( &processInfo, sizeof( processInfo ) );
TCHAR params[ MAX_PATH ];
_itow_s( procID, params, MAX_PATH, 10 );
TCHAR path[ MAX_PATH ];
if( !::GetModuleFileName( bhoDll, path, MAX_PATH ) )
return FALSE;
#ifdef UNICODE
wchar_t* sp = wcsrchr( path, L'\\' ) + 1;
#elif
char* sp = strrchr( path, '\\' ) + 1;
#endif
lstrcpy( sp, TEXT( "ieb_start.exe" ) );
if( !::CreateProcess( path, params, NULL, NULL, FALSE,
CREATE_NO_WINDOW, NULL, NULL, &startInfo, &processInfo ) )
return FALSE;
::WaitForSingleObject( processInfo.hProcess, INFINITE );
::CloseHandle( processInfo.hThread );
::CloseHandle( processInfo.hProcess );
return TRUE;
}
As we can see, the new process is started in the usual way, but without the visible window. It also should be noted that we pass the ID of the browser's main process to the broker so it knows where it should inject the wrapper DLL.
The basic principle of DLL injection is to allocate memory in a remote process (using the VirtualAllocEx
API call) and store the path to the DLL we want to load there (using a WriteProcessMemory
call). The next thing we should do is to create a thread in the targeted process using the CreateRemoteThread
API call. We provide LoadLibrary
as the entry point for this thread and pass the address previously obtained by calling VirtualAllocEx
as a parameter.
BOOL InjectDll(DWORD processId, TCHAR* dllName)
{
if( !DebugPrivileges( TRUE ) )
return FALSE;
HANDLE process = ::OpenProcess( PROCESS_ALL_ACCESS, FALSE, processId );
DWORD error = ::GetLastError();
if( !process )
return FALSE;
TCHAR path[ MAX_PATH ];
if( !::GetModuleFileName( NULL, path, MAX_PATH ) )
{
::CloseHandle( process );
return FALSE;
}
#ifdef UNICODE
wchar_t* sp = wcsrchr( path, L'\\' ) + 1;
wcscpy_s( sp, path + MAX_PATH - sp, dllName );
#elif
char* sp = strrchr( path, '\\' ) + 1;
strcpy_s( sp, path + MAX_PATH - sp, dllName );
#endif
LPVOID address = ::VirtualAllocEx(
process, NULL, sizeof( path ), MEM_COMMIT, PAGE_READWRITE );
if( !address )
return FALSE;
if( !::WriteProcessMemory(
process, address, path, sizeof( path ), NULL ) )
{
::VirtualFreeEx( process, address, sizeof( path ), MEM_RELEASE );
::CloseHandle( process );
return FALSE;
}
HANDLE thread = ::CreateRemoteThread( process, NULL, 0,
(LPTHREAD_START_ROUTINE)::GetProcAddress(
::GetModuleHandle( L"Kernel32" ), "LoadLibraryW" ),
address, 0, NULL );
if( !thread )
{
::VirtualFreeEx( process, address, sizeof( path ), MEM_RELEASE );
::CloseHandle( process );
return FALSE;
}
::WaitForSingleObject( thread, INFINITE );
::VirtualFreeEx( process, address, sizeof( path ), MEM_RELEASE );
::CloseHandle( thread );
::CloseHandle( process );
DebugPrivileges( FALSE );
return TRUE;
}
Silent Elevation of Broker Process
To start the broker process at medium level integrity without a prompt, we need to create an elevation policy. Each policy has to have its GUID. To create a policy, we have to make a new Registry key with the policy GUID as its name. In the new key, we should set three values:
AppPath
(DWORD
) - path to the broker process.AppName
(REG_SZ
)- file name of the executable file.Policy
(REG_SZ
)- defines how Internet Explorer will spawn the broker process; we set this value to '3' which instructs Explorer to launch the broker process silently.
These keys and values should be added during the BHO's registration process. To do that, we need to modify the registration script (IEBarBHO.rgs file) and add the following code:
HKLM
{
NoRemove SOFTWARE
{
NoRemove Microsoft
{
NoRemove 'Internet Explorer'
{
NoRemove 'Low Rights'
{
NoRemove ElevationPolicy
{
ForceRemove '{c6c528cd-8c93-494d-8583-38821b575da9}'
{
val AppPath = s '%MODULEPATH%'
val AppName = s 'ieb_start.exe'
val Policy = d '3'
}
}
}
}
}
}
}
The replaceable parameter %MODULEPATH%
is used to locate the broker's executable file. To make it work, we need to modify our BHO class and remove the default UpdateRegistry
method provided by the DECLARE_REGISTRY_RESOURCEID
macro and provide our own:
extern TCHAR g_ModulePath[ MAX_PATH ];
_ATL_REGMAP_ENTRY CIEBarBHO::RegEntries[] =
{
{ OLESTR( "MODULEPATH" ), g_ModulePath },
{ NULL, NULL }
};
HRESULT CIEBarBHO::UpdateRegistry(BOOL bRegister)
{
return ATL::_pAtlModule->UpdateRegistryFromResource(
IDR_IEBARBHO, bRegister, RegEntries );
}
g_ModulePath
is updated in the DllMain
function and it stores the path in which the BHO's DLL is stored. We also need to remove the call to the DECLARE_REGISTRY_RESOURCEID
macro from our class definition.
Inter-process Communication
Since we cannot send Windows messages between processes running at different integrity levels because of UIPI (User Interface Privilege Isolation), we should use another form of IPC.
This example uses file mappings to provide shared memory between tab processes and the browser's main process; for more advanced forms of communication, other IPC techniques can be used. The important question when creating a communication object is who owns it. If it is a medium integrity level process (main process), lower integrity processes (tab processes) cannot access it if they are not specifically lowering the object's integrity (using SetNamedSecurityInfoW
). An easier route would be to let the creation of communication object to tab processes. That way, both processes can access the object without additional hassle. Only securable objects (like file mappings, pipes, etc.) are subject to integrity checks, unlike sockets which are not checked.
Code
Now that we have finished discussing stuff related to DLL injection, we can cover the actual code that allows us to manipulate the browser's toolbar.
Server
The CAddressBarAccessServer
class subclasses the browser's toolbar and provides an interface to manipulate it. The SendMessage
and PostMessages
methods send/post messages to the toolbar window.
LRESULT SendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{ return ::SendMessage( _addressBarWnd, uMsg, wParam, lParam ); }
LRESULT PostMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{ return ::PostMessage( _addressBarWnd, uMsg, wParam, lParam ); }
The CmdTargetWndProc
method should be modified to handle WM_COMMAND
messages sent from the toolbar when the user clicks a button. The method also notifies the proxy of the current tab about messages it receives. The AddrBarWndProc
method should handle messages sent to the toolbar itself.
BOOL CAddressBarAccessServer::CmdTargetWndProc(
LRESULT* lResult, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch( uMsg )
{
}
if( uMsg == WM_COMMAND )
{
if( _currentProcessID != ::GetCurrentProcessId() )
{
ATL::CComCritSecLock<CComAutoCriticalSection> lock(
_clientHandlersLock, true );
ClienMessageHandlersMap::iterator it =
_clientMessageHandlers.find( _currentProcessID );
if( it != _clientMessageHandlers.end() )
{
ForwardedMessage msg(
_currentProxyClient, uMsg, wParam, lParam );
it->second.first->Write( &msg, sizeof( msg ), FALSE );
}
}
else
( (CAddressBarAccessProxy*)_currentProxyClient )->
CmdTargetWndProc( lResult, uMsg, wParam, lParam );
}
return FALSE;
}
BOOL CAddressBarAccessServer::AddrBarWndProc(
LRESULT* lResult, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch( uMsg )
{
}
return FALSE;
}
We need to modify these two methods if we want to handle messages sent to and from the toolbar. But notice that they could be executing in a different process from the one which owns our BHO.
SetCurrentProxy
sets the proxy that will be notified about messages sent from the toolbar.
void CAddressBarAccessServer::SetCurrentProxy(
DWORD processID, UINT_PTR proxyClient)
{
ATL::CComCritSecLock<CComAutoCriticalSection> lock(
_clientHandlersLock, true );
_currentProcessID = processID;
_currentProxyClient = proxyClient;
}
The Load
method performs subcalssing. This method should be modified to load the required images from the resource DLL and insert buttons to the toolbar. The Unload
method removes subclassing.
void CAddressBarAccessServer::Load(
HWND addressBarWnd, HWND cmdTargetWnd,
DWORD processID, UINT_PTR proxyClient)
{
ATL::CComCritSecLock<CComAutoCriticalSection> lock(
_clientHandlersLock, true );
if( processID != ::GetCurrentProcessId() &&
_clientMessageHandlers.find( processID )==
_clientMessageHandlers.end() )
{
_clientMessageHandlers[ processID ] = ClienMessageHandlersEntry(
new CCommChannel( TEXT( "IeBarMsgPoint" ), processID ), 1 );
if( _clientMessageHandlers.size() == 1 )
{
_currentProcessID = processID;
_currentProxyClient = proxyClient;
}
}
if( ++_tabCounter == 1 )
{
_addressBarWnd = addressBarWnd;
_cmdTargetWnd = cmdTargetWnd;
_oldAddrBarWndProc = (WndProcPtr)::GetWindowLongPtr(
_addressBarWnd, GWLP_WNDPROC );
::SetWindowLongPtr(
_addressBarWnd, GWLP_WNDPROC, (LONG_PTR)AddrBarWndProcS );
_oldCmdTargetWndProc = (WndProcPtr)::GetWindowLongPtr(
_cmdTargetWnd, GWLP_WNDPROC );
::SetWindowLongPtr(
_cmdTargetWnd, GWLP_WNDPROC, (LONG_PTR)CmdTargetWndProcS );
_imageList = (HIMAGELIST)::SendMessage(
addressBarWnd, TB_GETIMAGELIST, (WPARAM)0, (LPARAM)0 );
_hotImageList = (HIMAGELIST)::SendMessage(
addressBarWnd, TB_GETHOTIMAGELIST, (WPARAM)0, (LPARAM)0 );
_pressedImageList = (HIMAGELIST)::SendMessage(
addressBarWnd, TB_GETPRESSEDIMAGELIST, (WPARAM)0, (LPARAM)0 );
InitButtons();
lock.Unlock();
::SendMessage( addressBarWnd, WM_SIZE, 0, 0 );
::SendMessage( cmdTargetWnd, WM_SIZE, 0, 0 );
}
}
void CAddressBarAccessServer::Unload(
DWORD processID, UINT_PTR proxyClient)
{
ATL::CComCritSecLock<CComAutoCriticalSection> lock(
_clientHandlersLock, true );
if( processID != ::GetCurrentProcessId() )
{
ClienMessageHandlersEntry& entry =
_clientMessageHandlers[ processID ];
if( --entry.second == 0 )
{
delete entry.first;
_clientMessageHandlers.erase( processID );
}
}
if( --_tabCounter == 0 )
{
::SetWindowLongPtr(
_addressBarWnd, GWLP_WNDPROC, (LONG_PTR)_oldAddrBarWndProc );
::SetWindowLongPtr(
_cmdTargetWnd, GWLP_WNDPROC, (LONG_PTR)_oldCmdTargetWndProc );
_addressBarWnd = _cmdTargetWnd = NULL;
for( ButtonsMap::iterator it = _buttons.begin();
it != _buttons.end(); ++it )
it->second.Destroy();
_buttons.clear();
if( _channel )
{
delete _channel;
_channel = NULL;
}
}
}
AddButton
inserts the button to the toolbar. We must provide three icons for the button for different button states: when the button is inactive, when the user hovers over the button, and when the button is pressed.
void AddButton(WORD id, HICON image, HICON hotImage, HICON pressedImage);
The InitButtons
method initializes and inserts the required buttons to the toolbar. We should modify this function to insert our own buttons.
void CAddressBarAccessServer::InitButtons()
{
HINSTANCE module;
GetModuleHandleEx(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(LPCWSTR)( &CAddressBarAccessServer::ProxyListen ), &module );
HICON icon = ::LoadIcon( module, MAKEINTRESOURCE( IDI_ICON1 ) );
HICON hotIcon = ::LoadIcon( module, MAKEINTRESOURCE( IDI_ICON2 ) );
HICON pressedIcon = ::LoadIcon( module, MAKEINTRESOURCE( IDI_ICON2 ) );
AddButton( 0xc001, icon, hotIcon, pressedIcon );
}
The GetModuleHandleEx
API call obtains the handle to the module which contains resources (icons) that should be loaded. If there is tab isolation, it will return the handle to the wrapper DLL (ieb_wrap.dll); otherwise, it returns the handle to the BHO's DLL (ieb_bho.dll).
When the server runs in a separate process, it starts the listening thread that reads the requests from the IPC object and interprets them. The ProxyListen
method is the entry point of the thread. This method unpacks data sent through IPC and calls the appropriate server methods to handle the requests from proxies.
Proxy
The CAddressBarAccessProxy
class abstracts the location of the address bar server so the BHO should not worry whether it is located in the same process as the browser's address bar or not. If there is no tab isolation, the proxy object will just call the server's method directly; otherwise it will route the call through IPC to the server that resides in the main process.
The SendMessage
and PostMessage
methods just redirect calls to their counterparts of the server object. The SetCurrent
method notifies the server that this is the proxy server of the currently active tab.
void CAddressBarAccessProxy::SetCurrent()
{
if( _server )
_server->SetCurrentProxy( ::GetCurrentProcessId(), (INT_PTR)this );
else
{
SelectTabCmd cmd( ::GetCurrentProcessId(), (INT_PTR)this );
_cmdChannel->Write( &cmd, sizeof( cmd ) );
}
}
LRESULT CAddressBarAccessProxy::SendMessage(
UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if( _server )
return _server->SendMessage( uMsg, wParam, lParam );
SendMessageCmd cmd ( uMsg, wParam, lParam );
_cmdChannel->Write( &cmd, sizeof( cmd ) );
LRESULT result;
_cmdChannel->Read( &result, sizeof( result ), TRUE );
return result;
}
LRESULT CAddressBarAccessProxy::PostMessage(
UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if( _server )
return _server->PostMessage( uMsg, wParam, lParam );
PostMessageCmd cmd ( uMsg, wParam, lParam );
_cmdChannel->Write( &cmd, sizeof( cmd ) );
return 0;
}
To capture a tab change, we need to handle the DISPID_WINDOWSTATECHANGED
event sent by the browser object:
STDMETHODIMP CIEBarBHO::Invoke(DISPID dispidMember, REFIID riid,
LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
if( dispidMember == DISPID_WINDOWSTATECHANGED )
{
DWORD flags = pDispParams->rgvarg[1].intVal;
DWORD valid = pDispParams->rgvarg[0].intVal;
if( (valid & OLECMDIDF_WINDOWSTATE_USERVISIBLE) != 0 &&
(flags & OLECMDIDF_WINDOWSTATE_USERVISIBLE) != 0 &&
(valid & OLECMDIDF_WINDOWSTATE_ENABLED) != 0 &&
(flags & OLECMDIDF_WINDOWSTATE_ENABLED) != 0 )
_proxy->SetCurrent();
}
return S_OK;
}
CmdTargetWndProc
is the callback function used to notify the proxy about WM_COMMAND
sent by the browser's toolbar. Only the proxy of the currently selected tab will receive the message notification from the server. We should override this method if we want to handle these notifications.
virtual BOOL CmdTargetWndProc(
LRESULT* lResult, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ return 0; }
When the proxy is in a separate process, it starts another thread that listens for notifications about WM_COMMAND
messages which the server sends after the user clicks a button. The MessageHandlerListner
method is the entry point for the thread and it processes notifications by calling the CmdTargetWndProc
method.
DWORD CAddressBarAccessProxy::MessageHandlerListner(LPVOID param)
{
CCommChannel* channel = (CCommChannel*)param;
char buffer[ CCommChannel::SECTION_SIZE ];
ForwardedMessage* msg = (ForwardedMessage*)buffer;
while(channel->Read( buffer, CCommChannel::SECTION_SIZE ) )
{
LRESULT result;
( (CAddressBarAccessProxy*)msg->_proxyClient )->CmdTargetWndProc(
&result, msg->_uMsg, msg->_wParam, msg->_lParam );
}
return 0;
}
IPC
The CCommChannel
class encapsulates IPC between tab processes and the browser's main process. The most important methods are Read
and Write
which reads and writes the shared memory. The Read
method waits for data to become available before it reads them. The Write
method waits for the shared memory to become available (previously written data has to be read before) and then it writes new data, after which it signals that new data is available.
BOOL CCommChannel::Read(
LPVOID data, DWORD dataSize, BOOL response)
{
::WaitForSingleObject(
_events[ response ? RESPONSE_AVAILABLE : REQUEST_AVAILABLE ],
INFINITE );
LPVOID source =
::MapViewOfFile( _section, FILE_MAP_ALL_ACCESS, 0, 0, dataSize );
if( !source )
{
if( !response )
::SetEvent( _events[ SERVER_AVAILABLE ] );
return FALSE;
}
::CopyMemory( data, source, dataSize );
BOOL ok = ::UnmapViewOfFile( source );
if( !response )
::SetEvent( _events[ SERVER_AVAILABLE ] );
return ok;
}
BOOL CCommChannel::Write(
LPVOID data, DWORD dataSize, BOOL response)
{
if( !response )
::WaitForSingleObject( _events[ SERVER_AVAILABLE ], INFINITE );
LPVOID destination =
::MapViewOfFile( _section, FILE_MAP_ALL_ACCESS, 0, 0, dataSize );
if( !destination )
{
if( !response )
::SetEvent( _events[ SERVER_AVAILABLE ] );
return FALSE;
}
::CopyMemory( destination, data, dataSize );
if( ::UnmapViewOfFile( destination ) )
{
::SetEvent(
_events[ response ? RESPONSE_AVAILABLE : REQUEST_AVAILABLE ] );
return TRUE;
}
else
{
::SetEvent(
_events[ response ? RESPONSE_AVAILABLE : SERVER_AVAILABLE ] );
return FALSE;
}
}
SetReady
marks a section as available for new writes. IsFirst
indicates whether the current process is the one which created the system's IPC object.
There are several others structures that are used to pack and unpack messages when IPC is used between the server and proxy.
Installing and Uninstalling BHO
To register BHO, we should execute the regsvr32 ieb_bho.dll command, and to uninstall, regsvr32 /u ieb_bho.dll. These commands require administrative privileges. All DLL and EXE files have to be located in the same directory. Internet Explorer may ask the user if it should allow our BHO to be loaded.
Conclusion
As we saw, getting your own buttons into the address bar is not an easy task as it seems. Also, relaying on undocumented behavior can get you into trouble and can give you a headache with each version and even patch of the browser. Another problem is that this employs techniques that some antivirus packages can deem as harmful, especially since it runs within the browser, which is a very common target of attacks. So this technique should be used with great care and probably deployed only in a controlled environment.