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 extension is to add custom buttons next to address bar, but it is officially impossible to do in Internet Explorer. This could be a problem if we want to preserve same user interface for our extension across different browsers.

The solution is possible but it relays heavily on undocumented structures and 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 fallow the article, since basics about BHOs are not covered here.

More information about creating BHOs.

Sneaking into Internet Explorer's Address Bar

Finding Address Bar Window

The first this we should do is to explore window structure of Internet Explorer. Microsoft Spy++ is perfect tool for the job. To locate toolbar window just use Window Search tool of Spy++ (Search -> Find Window...) and drag and drop on the toolbar next to address edit box. Notice that "Search" button (magnifying glass) is not the part of toolbar but address combo box.

After we select toolbar window Spy++ reveals us this window hierarchy:

As we can see two of the windows are interesting to use:

  • the first one receives and handles WM_COMMAND messages sent from toolbar when user clicks button. Its class name is "Address Band Root"
  • the second is toolbar itself, which we want manipulate. Its class name is "ToolbarWindow32"

Finding these windows is implemented by 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 handle to main browser window and stores handles to located windows. It returns FALSE if it is unable to locate required windows. This procedure works only with Internet Explorer 7 or higher. Previous versions of Internet Explorer have similar window structure but not entirely the same so this function is not compatible.

Finding browser's main window is fairly trivial task. We just need to invoke get_HWND on 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 handle to the toolbar we can manipulate it like standard one, but we also need to subclass it if we want fine control over it. To do this we just need to replace function that handles messages directed to the window with our own, but we need to store old function so it can handle messages that we are not interested in handling.

GetWindowLongPtr API call with GWLP_WNDPROC as second parameter is used to obtain current function that handles messages. GetWindowLongPtr call sets new message handler. Our function has to call old one for all messages that we do not want to handle. To do so we need to invoke CallWindowProc and pass it pointer to old function which we replaced along with other information about received message.

It is not enough to subclass just toolbar window, because WM_COMMAND generated when user clicks on a button are sent to another window. To be able to handle user clicks on newly added buttons we need to subclass 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)
{
  // 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 Protected Mode and one of the consequences is that each tab has its own process which is separated from the process that host browser's main window (whose part is address bar). This is the first bad news for us. Our BHO are hosted within tab process for each one, meaning we have to cross process boundaries in order to get to browser's tool bar. This is where (cue the music) DLL injection comes into play.

Now straightforward implementations would be for our BHO to inject another DLL into 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 lower integrity process cannot interference with the higher integrity process. Internet Explorer spawns tab processes at lowest integrity level while the process that hosts main window running at medium level.

Fortunately this is where the good new start coming. When tab process spawns new process it is created at medium level - same integrity level as the process we are trying to reach. So to make DLL injection to work we just need to create broker process whose sole purpose is to inject DLL in main process of the browser. All good and well, but starting higher level processes then the level at which parent process is running while in Protected Mode will show user a prompt asking whether it should proceeded:

It is possible to suppress this prompt by creating elevation policy for broker process. Elevation Policy is group of registry keys and values located at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy. Creating policy is done in registration script of our BHO and it will be described later.

More information about Protected Mode in Internet Explorer.

Implementation

All code responsible for browser's toolbar manipulation is isolated in separate static library named ieb_lib. This library is part of BHO's DLL. It is also linked with auxiliary DLL which represents wrapper so it can be injected into browser's main process.

ieb_start.exe represents broker process that injects wrapper DLL into browser's main process.

DLL Injection

Injection starts by spawning broker process which performs 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, new process is started in a usual way, but without visible window. It also should be noted that we pass ID of browser's main process to the broker so it knows where it should inject wrapper DLL.

Basic principle of DLL injection is to allocate memory in remote process (using VirtualAllocEx API call) and store path to DLL we want to load there (using WriteProcessMemory call). Next thing we should do is to create thread in targeted process using CreateRemoteThread API call. We provide LoadLibrary as entry point for this thread and pass address previously obtained by calling VirtualAllocEx as 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;
}

Mor information about DLL injection technique used here.

Silent Elevation of Broker Process

To start broker process at medium level integrity without a prompt we need to create elevation policy. Each policy has to have its GUID. To create policy we have make new registry key with policy GUID as its name. In the new key we should set three values:

  • AppPath (DWORD) - path to broker process.
  • AppName (REG_SZ)- file name of executable file.
  • Policy (REG_SZ)- defines how Internet Explorer will spawn broker process. We set this value to ‘3' which instructs Explorer to launch broker process silently.

These keys and values should be added during BHO's registration process. To do that we need modify registration script (IEBarBHO.rgs file) and add fallowing 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 broker's executable file. To make it work we need to modify our BHO class and remove default UpdateRegistry method provided by 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 DllMain function and it stores path in which BHO's DLL is stored. We also need to remove call to DECLARE_REGISTRY_RESOURCEID macro from our class definition.

More information about replaceable parameters.

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 browser's main process, for more advanced form of communication other IPC techniques can be used. The important question when creating communication object is who owns it. If it is medium integrity level process (main process), lower integrity processes (tab processes) cannot access it if they are not specifically lowering object's integrity (using SetNamedSecurityInfoW). Easier route would be to let creation of communication object to tab processes. That way both processes can access 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 finished discussing stuff related to DLL injection we can cover actual code that allows us to manipulate browser's toolbar.

Server

CAddressBarAccessServer class subclasses browser's toolbar and provides interface to manipulate it.

SendMessage and PostMessages methods sends/posts messages to 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 ); }
CmdTargetWndProc method should be modified to handle WM_COMMAND messages sent from toolbar when user clicks a button. The method also notifies proxy of the current tab about messages it receives. AddrBarWndProc method should handle messages sent to toolbar itself.

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 want 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 proxy that will be notified about messages sent from toolbar.

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

  // store current proxy
  _currentProcessID = processID;
  _currentProxyClient = proxyClient;
}
Load method performs subcalssing. This method should be modified to load required images from resource DLL and inserts buttons to toolbar. 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() )
  {
    // 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 button to the toolbar. We must provide three icons for the button for different button states: when button is inactive, when user hovers over button and when the button is pressed.

void AddButton(WORD id, HICON image, HICON hotImage, HICON pressedImage);

InitButtons method initializes and insert 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 );

  /* 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 );
}

GetModuleHandleEx API calls obtains handle to module which contains resources (icons) that should be loaded. If there is tab isolation it will return handle to the wrapper DLL (ieb_wrap.dll), otherwise it returns handle to BHO's DLL (ieb_bho.dll).

When server runs in separate process it starts listening thread that reads request from IPC object and interprets them. ProxyListen method is entry point of the thread. This method unpacks data sent through IPC and calls appropriate server's methods to handle requests from proxies.

Proxy

CAddressBarAccessProxy class abstracts location of address bar server so the BHO should not worry whether it is located in the same process as browser's address bar or not. If there is not tab isolation proxy object will just call server's method directly, otherwise it will route call through IPC to server that resides in main process.

SendMessage and PostMessage methods just redirects calls to their counterparts of server object. SetCurrent method notifies server that this is the proxy server of 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 tab change we need to handle DISPID_WINDOWSTATECHANGED event sent by 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;

    // 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 callback function used to notify proxy about WM_COMMAND send by browser's toolbar. Only proxy of currently selected tab will receive message notification from 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 proxy is in separated process it starts another thread that listens for notifications about WM_COMMAND messages which server sends after user clicks button. MessageHandlerListner method is entry point for the thread and it process notification by calling 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

CCommChannel class encapsulates IPC between tab processes and browser's main process. The most important methods are Read and Write which reads and writes shared memory. Read method waits for data to become available before it reads them. Write method waits for shared memory to become available (previously written data have to be read before) and then it writes new data after which it signals that new data are available.

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 section as available for new writes. IsFirst indicates whether the current process is the one which created system's IPC object.

There are several others structures that are used to pack and unpack messages when IPC is used between server and proxy.

Installing and Uninstalling BHO

To register BHO we should execute 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 user if it should allow our BHO to be loaded.

Conclusion

As we saw, getting your own buttons into address bar is not easy task as it seems. Also, relaying on undocumented behavior can get you into troubles and can give you 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 browser which is very common target of attacks. So this technique should be used with a great care and probably deployed only in a controlled environment.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"