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

Intercept and Rewrite DNS Requests Using Regular Expressions

4.29/5 (14 votes)
2 Nov 20068 min read 1   1.8K  
A tool and code for injecting a DLL into a running Firefox process to rewrite DNS lookups.

Overview

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:

192.168.0.1    www.mycompany.com

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.

DnsHijack

My solution was to create an application I call DnsHijack. Here's what DnsHijack enables you to do:

  • It allows you to rewrite DNS requests for a single Windows process (in this case, it's hard-coded to firefox.exe, but the technique works equally well for any standard Winsock-using application).
  • You can rewrite to another DNS name instead of to just an IP address. There's no need to manually perform DNS lookups when creating the configuration file.
  • It supports Perl-compatible regular expressions (using the PCRE library and some C++ wrapper classes I created for my xp_pcre library). This means you can rewrite multiple DNS names using a single line in the configuration file. For example, the following line will cause DnsHijack to rewrite any DNS request ending in mycompany.com to staging.mycompany.com:
    replace   mycompany\.com$   staging.mycompany.com
  • In addition, you can capture portions of the originally-requested host name and substitute them into the rewritten host name using syntax similar to Perl's s/// (i.e. you can use the $1, $2, etc. variables to refer to captures). For example:
    replace   ^(.+)\.mycompany\.com$   $1.staging.mycompany.com
    This will result in:
    www.mycompany.comwww.staging.mycompany.com
    another.server.mycompany.comanother.server.staging.mycompany.com
  • The rewriting will only be in effect until you close the running instance of Firefox, so no more forgetting days later that you've made the change.

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:

  1. DnsHijack.exe: this is the application you run. It is responsible for locating the running Firefox process and injecting the DLL into it.
  2. 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.
  3. DnsHijackConfig.txt: the configuration file.
  4. 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:

  1. First, DnsHijack.exe uses the functions available in the Windows "Tool Help" library to find the Process ID of Firefox. (These functions are CreateToolhelp32Snapshot, Process32First and Process32Next.)
  2. Next, provided we found the Process ID, we open a handle to the Firefox process (using OpenProcess).
  3. Using this handle, we allocate some memory in Firefox's address space (using VirtualAllocEx).
  4. In this memory, we write the Unicode string "DnsHijackDll.dll" (using WriteProcessMemory).
  5. 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.
  6. 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 DllMain does:

  1. First, we parse the configuration file (DnsHijackConfig.txt) and create a std::vector containing all of the configuration rules.
  2. Next, provided the configuration file was parsed correctly, we call into the Detours library to hook the gethostbyname function.

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 gethostbyname, my_gethostbyname is actually called (because of the Detour) and performs the following:

  1. 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.
  2. If there is a match, and the rule is an "ignore" rule, skip the rest of the rules. No rewriting will take place.
  3. 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.
  4. Call 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).

Notes

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

Summary

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!

Revision History

  • 2006-10-24: It compiles! Ship it!

References

  • Programming Applications for Microsoft Windows, Fourth Edition by Jeffrey Richter. This is the definitive guide for DLL injection (among many, many other things).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here