Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / ATL

Manipulating Buttons in Internet Explorer's Address Bar

4.93/5 (15 votes)
13 Mar 2011CPOL12 min read 51.8K   2.1K  
How to gain control over the toolbar that hosts buttons located in Internet Explorer's address bar.

Image 1

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:

Image 2

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:

C++
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:

C++
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.

C++
typedef LRESULT (CALLBACK *WndProcPtr)(
  HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

/*...*/

WndProcPtr oldAddrBarWndProc = NULL;
WndProcPtr oldCmdTargetWndProc = NULL;

void SubclassAddressBar(HWND addressBarWnd, HWND cmdTargetWnd)
{
  // subclassing toolbar window
  oldAddrBarWndProc = (WndProcPtr)::GetWindowLongPtr(
    addressBarWnd, GWLP_WNDPROC );

  ::SetWindowLongPtr(
    addressBarWnd, GWLP_WNDPROC, (LONG_PTR)AddrBarWndProcS );

  // subclassing window that handles WM_COMMAND messages
  // sent from toolbar
  oldCmdTargetWndProc = (WndProcPtr)::GetWindowLongPtr(
    _cmdTargetWnd, GWLP_WNDPROC );

  ::SetWindowLongPtr(
    _cmdTargetWnd, GWLP_WNDPROC, (LONG_PTR)CmdTargetWndProcS );
}

/*...*/

LRESULT CAddressBarAccessServer::AddrBarWndProcS(
  HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  // here we should handle messages directed to the toolbar
  // that we are interested in

  return CallWindowProc( _instance->_oldAddrBarWndProc,
    hwnd, uMsg, wParam, lParam );
}

LRESULT CmdTargetWndProcS(
  HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  // here we should handle WM_COMMAND messages sent by the toolbar

  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:

Image 3

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

Image 4

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:

C++
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.

C++
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:

C++
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:

C++
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.

C++
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.

C++
BOOL CAddressBarAccessServer::CmdTargetWndProc(
     LRESULT* lResult, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch( uMsg )
  {
    /* TODO: ADD TOOLBAR COMMAND MESSAGE HANDLING THAT */
    /* IS EXECUTED WITHIN THE PROCESS WHICH HOSTS TOOLBAR */
    /* RETURN TRUE IF MESSAGE SHOULD NOT BE PROCESSED FURTHER */
  }

  if( uMsg == WM_COMMAND )
  {
    // should we use IPC or access client proxy directly
    if( _currentProcessID != ::GetCurrentProcessId() )
    {
      ATL::CComCritSecLock<CComAutoCriticalSection> lock(
        _clientHandlersLock, true );

      // send notification to listening thread
      // of process which owns client proxy
      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 )
  {
    /* TODO: ADD TOOLBAR MESSAGE HANDLING THAT */
    /* IS EXECUTED WITHIN THE PROCESS WHICH HOSTS TOOLBAR */
    /* RETURN TRUE IF MESSAGE SHOULD NOT BE PROCESSED FURTHER */
  }

  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.

C++
void CAddressBarAccessServer::SetCurrentProxy(
  DWORD processID, UINT_PTR proxyClient)
{
  ATL::CComCritSecLock<CComAutoCriticalSection> lock(
    _clientHandlersLock, true );

  // store current proxy
  _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.

C++
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() )
  {
    // add IPC channel for proxy if it is in a different process
    _clientMessageHandlers[ processID ] =  ClienMessageHandlersEntry(
      new CCommChannel( TEXT( "IeBarMsgPoint" ), processID ), 1 );

    if( _clientMessageHandlers.size() == 1 )
    {
      _currentProcessID = processID;
      _currentProxyClient = proxyClient;
    }
  }

  // do it only the first tab of the browser is initialized
  if( ++_tabCounter == 1 )
  {
    _addressBarWnd = addressBarWnd;
    _cmdTargetWnd = cmdTargetWnd;

    // subclass windows
    _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 );
        
    // get toolbar's image lists
    _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 );

    // add required buttons
    InitButtons();

    lock.Unlock();

    // refreshes size of the toolbar
    ::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() )
  {
    // destory IPC channel between proxy and server
    // if they are in different processes
    ClienMessageHandlersEntry& entry =
      _clientMessageHandlers[ processID ];
    if( --entry.second == 0 )
    {
        delete entry.first;
        _clientMessageHandlers.erase( processID );
    }
  }

  // if there's no more tabs when should clear changes made to toolbar
  if( --_tabCounter == 0 )
  {
    // reverese subclassing
    ::SetWindowLongPtr(
      _addressBarWnd, GWLP_WNDPROC, (LONG_PTR)_oldAddrBarWndProc );
    ::SetWindowLongPtr(
      _cmdTargetWnd, GWLP_WNDPROC, (LONG_PTR)_oldCmdTargetWndProc );

    _addressBarWnd = _cmdTargetWnd = NULL;

    // remove buttons
    for( ButtonsMap::iterator it = _buttons.begin();
      it != _buttons.end(); ++it )
      it->second.Destroy();

    _buttons.clear();

    // destory IPC channel which receives requests
    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.

C++
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.

C++
void CAddressBarAccessServer::InitButtons()
{
  HINSTANCE module;
  GetModuleHandleEx(
    GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
    (LPCWSTR)( &CAddressBarAccessServer::ProxyListen ), &module );

  /* INSERT BUTTONS TO TOOLBAR */

  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.

C++
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:

C++
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;

    // check whether the event is raised because tab became active
    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.

C++
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.

C++
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.

C++
BOOL CCommChannel::Read(
  LPVOID data, DWORD dataSize, BOOL response/* = FALSE*/)
{
  ::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/* = FALSE*/)
{
  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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)