1
votes

I'm trying to use NFQUEUE and Scapy to convert any UDP DNS request to TCP DNS requests and then respond the UDP DNS request with a crafted UDP packet based on TCP DNS resonse. This is the script that I wrote so far:

#! /usr/bin/env python2.7
from scapy.all import *
from netfilterqueue import NetfilterQueue
import os
import dns.resolver

myResolver = dns.resolver.Resolver()

def resolv_dns(payload):
    udp_query_pkt = IP(payload.get_payload())
    domain =  udp_query_pkt[DNS].qd.qname
    ip_addrs = myResolver.query(domain, "A", tcp=True) 

    if not udp_query_pkt.haslayer(DNSQR):
        payload.set_verdict(nfqueue.NF_ACCEPT)
    else:
        if domain in udp_query_pkt[DNS].qd.qname:
            print str(ip_addrs[0])
            udp_resp_pkt = IP(dst=udp_query_pkt[IP].src, src=udp_query_pkt[IP].dst)/\
                          UDP(dport=udp_query_pkt[UDP].sport, sport=udp_query_pkt[UDP].dport)/\
                          DNS(id=udp_query_pkt[DNS].id, qr=1, aa=1, qd=udp_query_pkt[DNS].qd,\
                          an=DNSRR(rrname=udp_query_pkt[DNS].qd.qname, ttl=10, rdata=str(ip_addrs[0])))
            send(udp_resp_pkt)
            payload.drop()

nfqueue = NetfilterQueue()
nfqueue.bind(1, resolv_dns) 

try:
    os.system("iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE --queue-num 1")
    print "[*] waiting for data"
    nfqueue.run()
except KeyboardInterrupt:
    os.system("iptables -D OUTPUT -p udp --dport 53 -j NFQUEUE --queue-num 1")
    pass

The problem with the script is that it doesn't work!

Actually I can see the correponding DNS packets in the wireshark and they seems okay: enter image description here

But I can't open any website! Actually the UDP DNS request timed out:

ebrahim@ebrahim:~$ dig www.xyw.com

; <<>> DiG 9.10.3-P4-Ubuntu <<>> www.xyw.com
;; global options: +cmd
;; connection timed out; no servers could be reached

What's wrong?


Update:

After @Pierre's answer, I changed the IPTable rule to send the received UDP DNS responses to the NFQUEUE (instead of sent DNS queries) and then I modified resolv_dns function as below(to replace IP address in the UDP DNS response with the new IP address which I received using TCP DNS query):

def resolv_dns(packet):
    pkt = IP(packet.get_payload())
    domain =  pkt[DNS].qd.qname
    ip_addrs = myResolver.query(domain, "A", tcp=True)
    pkt[DNS].an.rdata = str(ip_addrs[0])
    packet.set_payload(str(pkt))
    packet.accept()

But still it doesn't work!

1

1 Answers

1
votes

Your script would probably work if your box was a router intercepting forwarded packets. But since you are using the OUTPUT chain, I suppose the intercepted packets come from the local host. In this case, I think the original client will never get your forged answer.

In my opinion, your best option is to

  • Write a UDP server, that would listen on 127.0.0.1:5300 for example, which would read the DNS query (you can still do that with Scapy, using DNS(data_from_client)), resolve it (using TCP, as your script does), and send the response (again, you can send the data created with the DNS() call from your script).
  • Use iptables, but instead of intercepting outgoing packets with the NFQUEUE target, simply DNAT them to your server (something like iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to 127.0.0.1:5300).

Update: I have no idea why you insist on using NFQUEUE rather than a simple UDP server with DNAT (letting your IP stack & netfilter do the job), as that's probably the best way to do what you want, but you probably have a good reason. Please note that with your proposed second solution, you have to get a response from the DNS server for your script to work (so you have to have a working and reachable UDP DNS resolver).

In your second attempt, you modify the UDP data, so you have to compute again the IP & UDP checksum and length fields (using the server + DNAT solution, your kernel would do that job). You can do that with del pkt[IP].chksum, pkt[IP].len, pkt[UDP].chksum, pkt[UDP].len (Scapy will compute the correct values for you).