I was asked by our QA Ninja at work if I could come up with a way to make changes to the hosts file affecting only Firefox, without affecting any other applications running on the system, such as Internet Explorer. Frequently, people testing web applications will make changes to the hosts file in order to direct their web traffic to a staging or development server. However, changes to the hosts file affect the entire system. To have one browser be able to talk to the staging server while another is still talking to the production site would be quite useful during testing.
As a quick overview, here's how the hosts file works. Let's say, for example, you want to be able to type www.mycompany.com into your web browser but have the actual traffic go to staging.mycompany.com. In Windows, what you would normally do is to manually perform a DNS lookup for staging.mycompany.com and put the host name and IP address into your %SYSTEMROOT%\system32\drivers\etc\hosts file, like this:
Now, when any application performs a DNS lookup for www.mycompany.com, the DNS resolver will return 192.168.0.1, which, in this case, is actually the address for staging.mycompany.com.
This is an effective way to redirect requests. However, it is not without its shortcomings, namely:
- The change affects the entire system. For example, there is no way to have Firefox to use the entry in the hosts file while having Internet Explorer resolve DNS names as usual.
- You can't put the name of the destination server (e.g. staging.mycompany.com) in the hosts file; you must manually resolve the destination's IP address and put it in instead. Also (referring to the above example) this means that if the address for staging.mycompany.com changes frequently, you'll need to constantly update the hosts file entry.
- There is no way to override more than one address per line in the hosts file. This makes it rather tedious to redirect many host names from a single domain. For example, there's really no easy way to redirect *.mycompany.com to a different address.
- The change is persistent. Additions to the hosts file remain in effect until you remove them and it's easy to forget what you've changed from day to day. For some scenarios this may be desired, but when you need to test against multiple web environments, a long-forgotten hosts entry can and will bite you at the most inopportune time.
My solution was to create an application I call DnsHijack. Here's what DnsHijack enables you to do:
How It Works
DnsHijack works by injecting a DLL into the Firefox process and hooking the
gethostbyname function using the Detours library available from Microsoft Research.
gethostbyname is the Winsock function that will be called by Firefox when it needs to perform a DNS lookup.
There are four files involved:
- DnsHijack.exe: this is the application you run. It is responsible for locating the running Firefox process and injecting the DLL into it.
- DnsHijackDll.dll: this is the DLL that is injected into Firefox's virtual address space. When loaded, it parses the configuration file and uses the Detours library to patch the
gethostbyname Winsock function.
- DnsHijackConfig.txt: the configuration file.
- Detoured.dll: this is required to use the Detours library.
In order to use DnsHijack, you'll need to copy the above files to your Firefox directory (mine is C:\Program Files\Mozilla Firefox). Then, launch Firefox and run DnsHijack.exe. Here's what happens next:
- First, DnsHijack.exe uses the functions available in the Windows "Tool Help" library to find the Process ID of Firefox. (These functions are
- Next, provided we found the Process ID, we open a handle to the Firefox process (using
- Using this handle, we allocate some memory in Firefox's address space (using
- In this memory, we write the Unicode string "DnsHijackDll.dll" (using
- Next, we get the address for the
LoadLibraryW function in kernel32.dll (using
GetProcAddress). The address returned is actually an address in our current address space (DnsHijack.exe), not Firefox's. Fortunately, kernel32.dll is one of the first DLLs loaded into a Windows process, meaning it is almost always loaded at its preferred base address, which, in turn, means it is mapped to the same address in every process. So, even though our
LoadLibraryW pointer technically refers to DnsHijack's address space, it should work in Firefox's address space as well.
- Finally, in order to cause DnsHijackDll.dll to be loaded into the Firefox process, we create a thread in the Firefox process (using
CreateRemoteThread). Here's why this works: every thread needs a "start address" which is simply the address where the thread begins its execution (also called the "thread function"). The thread function for our new thread is set to the address of
LoadLibraryW, which we found in the previous step.
CreateRemoteThread also allows us to pass a single pointer-sized argument to the thread function and, fortunately, the function we've chosen as our thread function,
LoadLibraryW, takes exactly one argument. In this case, we pass the address of the string ("DnsHijackDll.dll") we wrote into Firefox's address space. This means we are effectively calling
LoadLibraryW(L"DnsHijackDll.dll") from inside the Firefox process. This loads the DLL and causes the
DllMain function in DnsHijackDll.dll to be called.
So, at this point we have code running in the Firefox process (specifically, our
DllMain function). Here's what
- First, we parse the configuration file (DnsHijackConfig.txt) and create a
std::vector containing all of the configuration rules.
- Next, provided the configuration file was parsed correctly, we call into the Detours library to hook the
When using the Detours library to hook a function, a function pointer is returned (which I store in the variable
real_gethostbyname) that we can use to call the original, unhooked function. We'll use this function pointer to perform the actual DNS lookup once we're done doing any rewriting.
The function that replaces
gethostbyname is named, in this case,
my_gethostbyname. So, when Firefox calls
my_gethostbyname is actually called (because of the Detour) and performs the following:
- For each of the rules parsed from the configuration file, try to match the DNS name being looked up against the regular expression in the rule.
- If there is a match, and the rule is an "ignore" rule, skip the rest of the rules. No rewriting will take place.
- If there is a match, and the rule is a "replace" rule, change the name being looked up to the new name and skip the rest of the rules.
real_gethostbyname, passing in either the original name, or if a "replace" rule was matched, the replacement DNS name. Either way, return the result to the caller (which, in this case, will be Firefox's code).
- Everything that can be statically-linked into the DLL has been. Since the majority of the DLL's setup work is done in
DllMain, causing another DLL to load could result in a deadlock since, during
DllMain's execution, the process-wide OS Loader lock is being held.
- If you're interested in seeing "debug" output, both during
DllMain and for each re-written DNS query, you can use Sysinternals' excellent DebugView utility. Just start it up before running DnsHijack.exe.
- To build this library yourself you'll need the Boost C++ libraries and the Detours library.
I hope that DnsHijack is useful application for web testers. In addition, for developers, it should serve as a good example of using techniques like DLL injection and function detouring. Please post any feature requests or bugs you find. Thanks!
- 2006-10-24: It compiles! Ship it!
- Programming Applications for Microsoft Windows, Fourth Edition by Jeffrey Richter. This is the definitive guide for DLL injection (among many, many other things).