06. April 2013 · Comments Off on Manipulating Buttons in Internet Explorer’s Address Bar · Categories: Programming · Tags: , , , ,

While Internet Explorer has nice extensibility support, it does not go as far as Mozilla Firefox or Google Chrome. One of the easy tasks to do when you build Firefox extensions is to add custom buttons next to the address bar, but officially, it is not possible 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.

Modified IE Toolbar

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:

ie_spy

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)
{
  // 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 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:

ie_prompt

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

ie_structure

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 )
  {
    /* 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.

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.

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.

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 );

  /* 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.

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;

    // 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.

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/* = 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 could be deemed harmful by some antivirus software, 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.

Downloads

Source code and Demo application

06. April 2013 · Comments Off on Bypassing “One Instance at a Time” Restrictions Introduced by Some Applications · Categories: Programming · Tags: , ,

Some applications, whatever the reasons are, do not allow the user to run multiple instances at the same time. This post demonstrates technique that can be used for circumvent these restrictions. In this example we will use Task Manager.

Note #1: The technique described here is only valid for Windows 7 (and maybe Vista); XP’s Task Manager uses a different technique.
Note #2: It is possible that developers of the targeted application had a good reason for introducing one instance-at-a-time restriction. For instance the application might require exclusive access to a file and another instance, if running, could corrupt data.

Background

When Task Manager begins execution, it tries to create a named kernel object (in this case, a mutex), and if there is already an object with the name, it assumes that another instance of Task Manager is already active and it terminates its execution.

Here’s an example of how it can be done:

if( ::CreateMutex( NULL, FALSE, 
  TEXT( "TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0" ) ) == NULL )
{
  if( ::GetLastError() == ERROR_ALREADY_EXISTS )
  {
    // task manager is already running - close current process
  }
}

// active task manage wasn't detected - continue

To find the kernel object we can use Process Explorer that has ability to list handles to all kernel objects used by the application.

Screenshot of Process Explorer

After a bit of experimenting we concluded that name of the offending mutex used by Task Manager is TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0. Now we can use Process Explorer and take the handle away from Task Manager and close it. This which will cause destruction for the mutex since it is the only handle to it. And we can call it a day and go do something else, but we’ll explore a little bit more and see how we can do it programmatically using undocumented Windows API.

Code

The first thing we must do is to find out the full name of the mutex, which is in a format like this: \Sessions\<SESSION_ID>\BaseNamedObjects\TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0, and we can obtain the session ID using the ProcessIdToSessionId API call.

DWORD sid;
if( !::ProcessIdToSessionId( ::GetCurrentProcessId(), &sid ) )
{
  wprintf( L"Unable to get session ID. Error code: %d\n",
    ::GetLastError() );
  return 2;
}

WCHAR mutexName[ MAX_PATH ];
mutexName[ 0 ] = 0;

// full name of the mutex
// (we want to mess only with session of the current user)
wcscat( mutexName, L"\\Sessions\\" );
_itow( sid, mutexName + wcslen( mutexName ), 10 );
wcscat( mutexName, L"\\BaseNamedObjects\\" );
wcscat( mutexName, MUTEX_NAME );

The basic idea is to destroy the mutex object before we start another instance of the Task Manager. To destroy it, we need to find all the handles to the object and close them. In the process, we need to use these undocumented API calls: NtQuerySystemInformation, NtQueryObject, and DuplicateHandle.

We use NtQuerySystemInformation to get the list of all open handles in the system, and then we iterate through the list, duplicating handles, to gain access to the object, and call NtQueryObject to get the name of the object. When we find the handle, we make a duplicate using the DuplicateHandle API call, but this time, we pass the DUPLICATE_CLOSE_SOURCE flag that instructs the system to close the original handle after the copy is made (effectively taking the ownership of the object), and immediately after that, we also close the new handle, and the end result is destroying the object.

// Searches for handle to an object with specified name 
// Returns -1 if it cannot obtain list of handles, 
// 0 if there's no handles to the object or 
// 1 if the object if found and handle is closed
INT SeekAndDestory(WCHAR* handleName)
{
  INT found = 0;

  // get list of opened handles
  DWORD size = 0;
  PSYSTEM_HANDLE_INFORMATION handles = 
    (PSYSTEM_HANDLE_INFORMATION)malloc(
      sizeof( SYSTEM_HANDLE_INFORMATION ) );
  if( !NT_SUCCESS( ::pNtQuerySystemInformation(
    (SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handles,
    sizeof( SYSTEM_HANDLE_INFORMATION ), &size ) ) )
  {
    free( handles );

    if( size == 0 )
      return -1;

    DWORD newSize = size + sizeof(HANDLE) * 512;
    handles = (PSYSTEM_HANDLE_INFORMATION)malloc( newSize );
    if( !NT_SUCCESS( ::pNtQuerySystemInformation( 
      (SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handles,
      newSize, &size ) ) )
    {
      free( handles );
      return -1;
    }
  }

  for( DWORD i = 0; i < handles->dwCount; i++ )
  {
    HANDLE process = ::OpenProcess( PROCESS_ALL_ACCESS, FALSE, 
      handles->Handles[ i ].dwProcessId );
    if( process )
    {
      HANDLE myHandle;

      if( ::DuplicateHandle( process, 
        (HANDLE)handles->Handles[ i ].wValue, ::GetCurrentProcess(),
        &myHandle, DUPLICATE_SAME_ACCESS, FALSE, 0 ) )
      {
        // get object name
        PPUBLIC_OBJECT_TYPE_INFORMATION nameInfo =
          (PPUBLIC_OBJECT_TYPE_INFORMATION)malloc( 
          sizeof( PUBLIC_OBJECT_TYPE_INFORMATION ) );
        if( !NT_SUCCESS( pNtQueryObject( myHandle, ObjectNameInformation,
          nameInfo, sizeof( PUBLIC_OBJECT_TYPE_INFORMATION ), &size ) ) )
        {
          free( nameInfo );

          if( (int)size <= 0 )
          {
            ::CloseHandle( myHandle );
            continue;
          }

          DWORD newSize = size;
          nameInfo = (PPUBLIC_OBJECT_TYPE_INFORMATION)malloc( newSize );
          if( !NT_SUCCESS( pNtQueryObject( myHandle, 
            ObjectNameInformation, nameInfo, newSize, &size ) ) )
          {
            ::CloseHandle( myHandle );
            continue;
          }
        }

        ::CloseHandle( myHandle );

        if( lstrcmp( handleName, nameInfo->TypeName.Buffer ) == 0 )
        {
          // take ownership of the handle
          // (copy handle and close original and then close the copy)
          if( ::DuplicateHandle( process, 
            (HANDLE)handles->Handles[ i ].wValue, ::GetCurrentProcess(),
            &myHandle, 0, FALSE,
            DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE ) )
          {
            ::CloseHandle( myHandle );
            found = 1;
          }
        }

        free( nameInfo );
      }

      ::CloseHandle( process );
    }
  }

  free( handles );
  return found;
}

Note: To get access to NtQuerySystemInformation and NtQueryObject, we need to use GetProcAddress because they are not available to the linker during the building process. The NtLib.h file contains the definitions required for using the undocumented API.

pfnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
pfnNtQueryObject pNtQueryObject = NULL;

BOOL LoadNtLib()
{
  pNtQuerySystemInformation =
    (pfnNtQuerySystemInformation)::GetProcAddress(
      GetModuleHandle( TEXT( "ntdll" ) ), "NtQuerySystemInformation" );
  pNtQueryObject = (pfnNtQueryObject)::GetProcAddress(
    GetModuleHandle( TEXT( "ntdll" ) ), "NtQueryObject" );

  return pNtQuerySystemInformation && pNtQueryObject;
}

Some more information about NtQuerySystemInformation can be found in this article: Listing Used Files.

Downloads

Source code and Demo application