Saturday, April 7, 2012

Using Browser Helper Objects to redirect domains to IPs

I've was recently requested to construct something that will simulate hosts file (see end of post for explanation about hosts file) for Internet Explorer (IE). For example, if the user entered www.google.com the browser should not go to www.google.com, but to a different IP, for example, 192.10.0.1 (so for example www.google.com/mysite would end up, 192.10.0.1/mysite). There was no need to fake the url, only to redirect certain urls to IPs. Since it only needed to work in IE and work without changing the hosts file itself, it was decided to use Browser Helper Object (BHO) for this task. Browser Helper Objets are sort of add-ons for IE with higher privileges than regular add-ons. They allow you to control various aspects of the browser and respond to events coming directly from it. You can also use them to add functionality to the regular explorer shell (but we will not get into this here). It is supported by all IEs since version 4, but certain functions are not available in version 5.5. and lower (if you are using IE 5.5 I'm sure you have other problems than finding which BHO function works for you).

To build the BHO I relied on two great MSDN articles. Building Browser Helper Objects with Visual Studio 2005 , and Browser Helper Objects: The Browser the Way You Want It . The first, as the name suggests is a practical step-by-step guide to building BHO. The second is a more in-depth paper explaining how BHO work. If you are just looking to build the BHO and be done with it should look at the first article and your BHO will be ready for use in no time (less then an hour). However, if you have some time I recommend reading the second one as well to understand how things work.
By the way, both articles are in C++, if you are looking for a C# examples you can check How to attach to Browser Helper Object (BHO) with C# in two minutes for a guide how to build the BHO and Using Internet Explorer from .NET on how control IE from .net applications.


In this post I will not get into how to build the BHO itself, the article Building Browser Helper Objects with Visual Studio 2005 explains it well enough. In this post I will show:
1. How to catch navigation events
2. Read the hostname from the url
3. Replace the hostname with a different IP
4. Tell the browser to go to the new domain


1. Show how catch navigation events


So after creating the initial BHO structure the first thing I wanted to do was catch any navigation changes. For this I used the OnBeforeNavigate2 event (You can find more BHO events at InternetExplorer Object) and needed to add a sink entry to my SINK_MAP in the header file:
BEGIN_SINK_MAP(HostsSimulatorBHO)
    SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate2)
END_SINK_MAP()
in addition I needed to add the deceleration for this function:
// DWebBrowserEvents2
void STDMETHODCALLTYPE OnBeforeNavigate2(IDispatch* pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT_BOOL* Cancel);

and of course the implementation of the function that will handle the event in the the cpp file:

void STDMETHODCALLTYPE HostsSimulatorBHO::OnBeforeNavigate2(IDispatch* pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT_BOOL* Cancel)
{       
    /*
        1. parse the url into its components InternetCrackUrl
        2. compare the hostname component to predefined hostname
        3. if hostname matches, replace with ip 
        4. otherwise continue without modifications
    */
}


Note: From now on all code parts are written inside the OnBeforeNavigate2 function.

2. Read the site we are going to

To parse the url to it's part I used the InternetCrackUrl which gives me the different parts of the URL. The advantages to using this function are that it also handles subdomains (i.e, it knows to handle "www.google.com" and "google.com") and the fact that somebody else did all the hard work for you. Please note that when calling this function you need to pre-build the data structure that it receives including memory allocation.

//get the url from the browser, was passed to function
CAtlString originalURL(*URL);
//INTERNET_MAX_HOST_NAME_LENGTH and URL_COMPONENTS are part of wininet.h
WCHAR hostname[INTERNET_MAX_HOST_NAME_LENGTH];
URL_COMPONENTS uc = { sizeof(URL_COMPONENTS) };
//initialize only the parts of the url that interest us (hostname)
uc.lpszHostName = hostname;
uc.dwHostNameLength = _countof(hostname);

if (InternetCrackUrl(originalURL.GetString(), originalURL.GetLength(), 0, &uc))
{
    //compare the hostname with domain, if it matches we replace
}
else
{
    //error handling
}

 3. Replace the domain with a different IP

ok, so we've got the hostname. now we'd like to check if it matches a predefined url, and if it does replace it with an IP.

//get the url from the browser, was passed to function
CAtlString originalURL(*URL);
//INTERNET_MAX_HOST_NAME_LENGTH and URL_COMPONENTS are part of wininet.h
WCHAR hostname[INTERNET_MAX_HOST_NAME_LENGTH];
URL_COMPONENTS uc = { sizeof(URL_COMPONENTS) };
//initialize only the parts of the url that interest us (hostname)
uc.lpszHostName = hostname;
uc.dwHostNameLength = _countof(hostname);

if (InternetCrackUrl(originalURL.GetString(), originalURL.GetLength(), 0, &uc))
{
   
   //compare the hostname with domain, if it matches we replace
   if (_wcsicmp(hostname, L"www.google.com") == 0)
   {
       //find index of hostname in the url
       int iHostIndex = originalURL.Find(hostname, 0);
   
       //build a new url with the hostname replaced by IP.
       CAtlString updatedURL(originalURL.Left(iHostIndex)+ L"192.10.0.1" + originalURL.Mid(iHostIndex + uc.dwHostNameLength));
    }
}
else
{
    //error handling
}

4. Tell the browser to go to the new domain

We will first need to cancel the current navigation before sending the new one to avoid messages to the user. We will use the Stop and Navigate methods. More methods can be found here .

//get the url from the browser, was passed to function
CAtlString originalURL(*URL);
//INTERNET_MAX_HOST_NAME_LENGTH and URL_COMPONENTS are part of wininet.h
WCHAR hostname[INTERNET_MAX_HOST_NAME_LENGTH];
URL_COMPONENTS uc = { sizeof(URL_COMPONENTS) };
//initialize only the parts of the url that interest us (hostname)
uc.lpszHostName = hostname;
uc.dwHostNameLength = _countof(hostname);

if (InternetCrackUrl(originalURL.GetString(), originalURL.GetLength(), 0, &uc))
{
 

   //compare the hostname with domain, if it matches we replace
   if (_wcsicmp(hostname, L"www.google.com") == 0)
   {
       //find index of hostname in the url
       int iHostIndex = originalURL.Find(hostname, 0);
 
       //build a new url with the hostname replaced by IP.
       CAtlString updatedURL(originalURL.Left(iHostIndex)+ L"127.0.0.0" + originalURL.Mid(iHostIndex + uc.dwHostNameLength));

        //cancel current navigation event
        *Cancel = VARIANT_TRUE;  
        CComQIPtr<IWebBrowser2> spWebBrowser(pDisp);
        spWebBrowser->Stop();          
        //send new navigation event to new url
        spWebBrowser->Navigate((BSTR)updatedURL.GetString(), Flags, TargetFrameName, PostData, Headers);


    }
}
else
{
    //error handling


If anyone needs to code to read the mock-up hosts file let me know and I'll post it as well.

"hosts" File

A host file allows you to map hostnames to IPs, so that for example if you enter www.google.com it will not go for the regular www.google.com site but to an IP you wrote. For further reading you can visit wikipedia.

No comments:

Post a Comment