James Stanley


dnstweak: quick local DNS spoofing tool

Tue 11 July 2023
Tagged: software

I spent most of today writing dnstweak, a program that temporarily inserts itself as the local DNS resolver (by writing to /etc/resolv.conf) and spoofs responses to selected DNS requests.

I wanted to give it a better name but everything I could think of was already taken by too many other people's weird hacky DNS tools.

Why?

Yesterday I was trying to test how a piece of software would behave if it received multiple A records for a given domain name, but not always in the same order. I couldn't change the DNS records for the domain in question, and setting up an alternative to test with is a lot of faff. Wouldn't it be easier to just type something like:

$ sudo dnstweak foo.example.com=127.0.0.1,1.2.3.4

And have it do what I want?

Well I didn't know of a tool to do that so I made one myself.

How?

It's written in go and has only one (immediate) dependency: Miek Gieben's dns library. The dns library made it very easy to write this program.

At startup it grabs the first nameserver line from /etc/resolv.conf (to use as the upstream resolver), and all search lines, stores the existing resolv.conf in memory, and writes a new resolv.conf that persists the search lines, and points nameserver at itself. When it exits, it restores the resolv.conf that it has stored in memory.

When a spoofed response has more than one IP address, they are shuffled. (Just because this is the behaviour I wanted to test - but it would be easy to make this configurable).

One cool feature is that it looks up the client addresses under /proc to work out which process each request originates from. I didn't actually need this feature but it was fun, and easy enough to do. You need to look in /proc/net/udp to find the inode corresponding to the client's address, and then look through all of the /proc/$pid/fd/* to find one that has the same inode, then you have found the PID of the client, and you can look in /proc/$pid/cmdline to find out the name of the process.

Why not?

The most fragile part is that if it crashes then it won't restore your original resolv.conf. I recommend taking a backup of your resolv.conf before you start. Probably dnstweak should make such a backup itself, but would need to be careful to make sure a second run of dnstweak doesn't overwrite the first (good) backup with a copy of the resolv.conf that the previous run created!

Also, maybe it should detect when systemd is managing the nameserver and try to cooperate with systemd instead of unilaterally trampling over resolv.conf. And maybe it should watch /etc/resolv.conf with inotify so that it can at least warn you if something else changes it back.

Example session

Open 2 terminal windows. We'll run dnstweak in the first and ping in the second.

First terminal:

$ sudo ./dnstweak example.com=127.0.0.1
2023/07/11 20:41:35 dnstweak starts
2023/07/11 20:41:35 listening on 127.0.0.1:53
2023/07/11 20:41:35 using 127.0.0.53:53 as upstream resolver

dnstweak stays running. It has inserted itself as the local resolver by modifying /etc/resolv.conf.

If you run dnstweak as a non-root user, it will still work as a DNS server, but it won't be able to listen on port 53 or insert itself into /etc/resolv.conf.

In the other terminal, start a ping to example.com:

$ ping example.com

Now we get some log output from dnstweak in our first terminal:

2023/07/11 20:41:37 127.0.0.1:48439 (ping/9975): A example.com: 127.0.0.1 (overridden)

A request came from 127.0.0.1:48439, which is a process called ping with PID 9975. It was an A lookup for example.com, and we returned 127.0.0.1. Back in the other terminal:

PING example.com (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.020 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.037 ms

Great success.

More

There is more information, and Linux x86-64 binaries, in the github repo.



If you like my blog, please consider subscribing to the RSS feed or the mailing list: