Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

Iodine DNS Tunneling – Not Just for Exfiltration!

Continuing with my theme of VPNs, I thought I’d share some Iodine DNS tunneling this week.

Iodine DNS Tunneling – Introduction

If you are not aware, Iodine is a great tool released by Erik Ekman and Bjorn Andersson that will do IPv4 tunneling over DNS.

This is useful for evading captive portals, exfiltration, or just another layer of obfuscation/privacy.

For another walkthrough, I recommend the following blog post

Installation

First, I setup iodine on the server that I wanted to use.

root@server:~# apt-get install iodine
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
  fping oping ipcalc
The following NEW packages will be installed:
  iodine
0 upgraded, 1 newly installed, 0 to remove and 13 not upgraded.
Need to get 101 kB of archives.
After this operation, 251 kB of additional disk space will be used.
Get:1 http://mirrors.digitalocean.com/ubuntu/ trusty/universe iodine amd64 0.6.0~rc1-18 [101 kB]
Fetched 101 kB in 0s (157 kB/s)
Preconfiguring packages ...
Selecting previously unselected package iodine.
(Reading database ... 92366 files and directories currently installed.)
Preparing to unpack .../iodine_0.6.0~rc1-18_amd64.deb ...
Unpacking iodine (0.6.0~rc1-18) ...
Processing triggers for man-db (2.6.7.1-1ubuntu1) ...
Processing triggers for ureadahead (0.100.0-16) ...
Setting up iodine (0.6.0~rc1-18) ...

I also installed the application on my local Kali box for testing in the same way.

Configuration and Running

With iodine installed on both systems, it was time to start-up the server. As mentioned in the previously linked walkthrough, I decided to go with a subdomain instead of my full domain name.

root@server:~# iodined -f 10.0.0.1 t1.myserver.com
Enter password:
Opened dns0
Setting IP of dns0 to 10.0.0.1
Setting MTU of dns0 to 1130
Opened UDP socket
Listening to dns for domain t1.myserver.com

Connection Errors

Unfortunately, when I first tried to connect to my server, I was receiving a lot of bad handshake errors.

root@kali:~# iodine -f -r 1.2.3.4 t1.myserver.com
Enter password: 
Opened dns0
Opened IPv4 UDP socket
Sending DNS queries for t1.myserver.com to 1.2.3.4
Autodetecting DNS query type (use -T to override).
Using DNS type NULL queries
Version ok, both using protocol v 0x00000502. You are user #0
Received bad handshake
Retrying login...
Received bad handshake
Retrying login...
Received bad handshake
Retrying login...
Received bad handshake
Retrying login...
Received bad handshake
Retrying login...
iodine: couldn't login to server

I found a blog post that mentioned fixing this problem with the ‘-c’ flag. Also, the README says that this flag should be used, “If there is a chance you’ll be using an iodine tunnel from unexpected environments, start iodined with a -c option.”

After checking the actual binary, it seems that the flag would fix my errors.

-c to disable check of client IP/port on each request

It Works!

With the -c flag added to my server, I re-ran the application on my client. As you can see, it connected just fine this time.

root@kali:~# iodine -f -r 1.2.3.4 t1.myserver.com
Enter password: 
Opened dns0
Opened IPv4 UDP socket
Sending DNS queries for t1.myserver.com to 1.2.3.4
Autodetecting DNS query type (use -T to override).
Using DNS type NULL queries
Version ok, both using protocol v 0x00000502. You are user #0
Setting IP of dns0 to 10.0.0.2
Setting MTU of dns0 to 1130
Server tunnel IP is 10.0.0.1
Skipping raw mode
Using EDNS0 extension
Switching upstream to codec Base128
Server switched upstream to codec Base128
No alternative downstream codec available, using default (Raw)
Switching to lazy mode for low-latency
Server switched to lazy mode
Autoprobing max downstream fragment size... (skip with -m fragsize)
768 ok.. ...1152 not ok.. 960 ok.. 1056 ok.. 1104 ok.. ...1128 not ok.. 1116 ok.. will use 1116-2=1114
Setting downstream fragment size to max 1114...
Connection setup complete, transmitting data.

Note that I was getting a lot of “SERVFAIL” errors. This is likely due to my DNS server being impatient and timing out quickly. That said, it didn’t seem to effect anything, and iodine automatically updates the timeout interval.

iodine: Got SERVFAIL as reply: server failed or recursion timeout
iodine: Hmm, that's 1. Your data should still go through...
iodine: Got SERVFAIL as reply: server failed or recursion timeout
iodine: Hmm, that's 2. Your data should still go through...
iodine: Got SERVFAIL as reply: server failed or recursion timeout
iodine: Hmm, that's 3. Your data should still go through...
iodine: Got SERVFAIL as reply: server failed or recursion timeout
iodine: Hmm, that's 4. Your data should still go through...
iodine: Got SERVFAIL as reply: server failed or recursion timeout
iodine: I think 5 is too many. Setting interval to 1 to hopefully reduce SERVFAILs. But just ignore them if data still comes through. (Use -I1 next time on this network.)
iodine: Got SERVFAIL as reply: server failed or recursion timeout
iodine: Got SERVFAIL as reply: server failed or recursion timeout
iodine: Got SERVFAIL as reply: server failed or recursion timeout

Verification

With the server and client both functioning, it was time to test the tunnel.

First, I verified my current IP and location information.

root@kali:~# curl ipinfo.io
{
  "ip": "40.x.x.x",
  "hostname": "x.ip.windstream.net",
  "city": "< ... snip ... >",
  "region": "Massachusetts",
  "country": "US",
  "loc": "< ... snip ... >",
  "postal": "< ... snip ... >",
  "phone": "< ... snip ... >",
  "org": "AS7029 Windstream Communications LLC"
}

I also verified that my new ‘dns0’ interface was up, and had a proper IP address.

root@kali:~# ifconfig dns0
dns0: flags=4305 mtu 1130
inet 10.0.0.2 netmask 255.255.255.224 destination 10.0.0.2
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1 bytes 48 (48.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

Next, I created an SSH tunnel between my client and the server, to act as a SOCKS proxy. The -N flag instructs SSH to not start a shell, as this will just function as a transparent proxy, and the -D flag configures the dynamic port forwarding.

root@kali:~# ssh -N -D 8080 [email protected]
The authenticity of host '10.0.0.1 (10.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:BMLJjcXWsYSzkeBeW17+bWZx9hoa2ylQVpS8NnywqWQ.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.0.1' (ECDSA) to the list of known hosts.
[email protected]'s password: 

With my proxy setup, I requested my updated IP information using the local proxy settings. As you can see, I was now connecting from my downstream server!

root@kali:~# curl --socks5 127.0.0.1:8080 http://ipinfo.io
{
  "ip": "1.2.3.4",
  "city": "< ... snip ... >",
  "region": "New Jersey",
  "country": "US",
  "loc": "< ... snip ... >",
  "postal": "< ... snip ... >",
  "phone": "< ... snip ... >",
  "org": "AS14061 DigitalOcean, LLC"
}

Bonus – Viewing the Requests

Once I had everything working, I was curious about what the real requests looked like.

First, I fired up tshark and listened to any DNS queries on my ethernet interface. As you can see, the client was sending some very interesting looking queries to my iodine server.

root@kali:~# tshark -T fields -e dns.qry.name -i eth1
Running as user "root" and group "root". This could be dangerous.
tshark: Lua: Error during loading:
 [string "/usr/share/wireshark/init.lua"]:32: dofile has been disabled due to running Wireshark as superuser. See https://wiki.wireshark.org/CaptureSetup/CapturePrivileges for help in running Wireshark as an unprivileged user.
Capturing on 'eth1'
paaqjrvi.t1.myserver.com
paaqjrva.t1.myserver.com
paaqjrvq.t1.myserver.com
paaqjrvi.t1.myserver.com
ipinfo.io
ipinfo.io
ipinfo.io
ipinfo.io
ipinfo.io
0ecbg82\276zGb\325\375aabacuGa\306dmeabagm\373\277Gaaekaaay4\276aw\3361\347\321\344VGK\370Gdajx\362\370a.aaqiGvQoFr\316\365\372i\370m6\347\313y\277\343\333v\3706\330p\326\336\331n\311\345\276\333s\347x\344\337\372lGXqoL\366\374\325W2\320Rdp.I\347MiHJZY5S3\365x\315\367qH4\353\303F4n1F\303I\325F\304\323\3676J\322\3510y\335R\340d\300\360\361\326\343\362k\274J\340\370\350\347K\327.g\336\361PO23\366\321\367\276.t1.myserver.com
paaqjrvq.t1.myserver.com
0ecbg82\276zGb\325\375aabacuGa\306dmeabagm\373\277Gaaekaaay4\276aw\3361\347\321\344VGK\370Gdajx\362\370a.aaqiGvQoFr\316\365\372i\370m6\347\313y\277\343\333v\3706\330p\326\336\331n\311\345\276\333s\347x\344\337\372lGXqoL\366\374\325W2\320Rdp.I\347MiHJZY5S3\365x\315\367qH4\353\303F4n1F\303I\325F\304\323\3676J\322\3510y\335R\340d\300\360\361\326\343\362k\274J\340\370\350\347K\327.g\336\361PO23\366\321\367\276.t1.myserver.com

After reading through the operational info, it seemed that application encoded the default queries in Base128. As I couldn’t get CyberChef to decode these properly, I decided to try to view the queries in Base64.

root@kali:~# iodine -f -O Base64 -r 1.2.3.4 t1.myserver.com
Enter password: 
Opened dns0
Opened IPv4 UDP socket
Sending DNS queries for t1.myserver.com to 1.2.3.4
Autodetecting DNS query type (use -T to override).
Using DNS type NULL queries
Version ok, both using protocol v 0x00000502. You are user #1
Setting IP of dns0 to 10.0.0.3
Setting MTU of dns0 to 1130
Server tunnel IP is 10.0.0.1
Skipping raw mode
Using EDNS0 extension
Switching upstream to codec Base128
Server switched upstream to codec Base128
Switching downstream to codec Base64
Server switched downstream to codec Base64
Switching to lazy mode for low-latency
Server switched to lazy mode
Autoprobing max downstream fragment size... (skip with -m fragsize)
768 ok.. ...1152 not ok.. 960 ok.. 1056 ok.. 1104 ok.. ...1128 not ok.. 1116 ok.. will use 1116-2=1114
Setting downstream fragment size to max 1114...
Connection setup complete, transmitting data.

Unfortunately, iodine was still encoding the queries in something that I couldn’t understand or decode.

12fby82\3122hb\276\356k\326rn\304\351\305\276\356Wk\352\304dl\276\336dy\306\342V2y\340B\316\313\3018FB\316\352\324Kml\300f\335\304Qje\304hR6.\317\350HP\375\304\307v\341\363\3312C\3552\351\313Z\300\367W\362\324\361\332\314\354\274f\344\375C\302\353\327\324i\3153\363\352q\332\340V\353\3306\363\354tWb\3754p\337.\336.t1.myserver.com
12fby82\3122hb\276\356k\326rn\304\351\305\276\356Wk\352\304dl\276\336dy\306\342V2y\340B\316\313\3018FB\316\352\324Kml\300f\335\304Qje\304hR6.\317\350HP\375\304\307v\341\363\3312C\3552\351\313Z\300\367W\362\324\361\332\314\354\274f\344\375C\302\353\327\324i\3153\363\352q\332\340V\353\3306\363\354tWb\3754p\337.\336.t1.myserver.com

This was mostly just an exercise in curiosity, but let me know if you know how to decode these queries!

Iodine DNS Tunneling – Conclusion

Iodine was fairly simple to get running once I had everything configured. I don’t think I’ll use this as often as my standard VPN solutions, but it is a nice trick to have in the bag, especially when I’m behind something like a captive portal.

I’ve still got to finish work on the other vulnserver commands, so don’t expect a new one of those posts too quickly.

Other than that, as always, let me know if you have any ideas for tools, research, or a post in general!

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.