Having fun with NFQUEUE and Scapy

Hi guys,
sorry for the long silence! I know, I know, it has been long time from my last entry in this blog. During this year I have been very busy with my last year at university and thus I focused all my attention to pass the exams and to find a good final project and so I spent less time on security and related projects.

In this post I am going to explain how to use nfqueue python bindings to fool a service or a malware when it tries to use DNS and/or ICMP, it is just a proof of concept, nothing more. This can be useful in different kind of situations, for example to see the behaviour of a sample when it tries to contact a server that is no more up, or when again the same sample tries to test if it is able to surf the net pinging google.
nfqueue bindings is an handy set of functions to read the target NFQUEUE of Iptables. Using this target we are able to decide to accept or drop a packet from userland, in addition we can modify the packet and send it again for example. Let’s see how to use NFQUEUE. First of all it is necessary to set a rule using Iptables in order to specify which packets
will be involved in the jump for that target, let’s set it

 iptables -A OUTPUT -p tcp –dport 6667 -j NFQUEUE

For example this rule means that all the outgoing TCP traffic with destination port 6667 (generally IRC port) should jump to the target NFQUEUE at the queue num 0 (by default the queue is 0 otherwise you have to specify it manually). Once the rule is set we have to use the python bindings. The functions are not well documented but fortunately there are some good tutorials on the net to figure out the main features such as  Zardus’ Blog, Malware Forge etc.
Now it is time to see how we can read that sort of queue, let’s see the code below:


def main():
 q = nfqueue.queue()
 q.open()
 q.bind(socket.AF_INET)
 q.set_callback(process)
 q.create_queue(0)

 try:
 q.try_run()
 except KeyboardInterrupt:
 print "Exiting..."
 q.unbind(socket.AF_INET)
 q.close()
 sys.exit(1)

main()

Basically this code opens the NFQUEUE and parses the packets within it through the callback function, in our case it is called “process”.
In the callback we can do whatever we want. “process” is defined as:

def process(i, payload):
 data = payload.get_data()
 p = IP(data)
 ... etc ...

for us it is very important the payload that means the IP packet.
We are ready to play a bit with ICMP. My idea is to write a rule for iptables to intercept ICMP echo request packets, then we drop them, but we send to the client the expected ICMP echo reply packet. In this way the client does not contact any external server. The code is quite simple and it uses scapy. If you don’t know Scapy it is quite simple and you can have more information here, in particular for us it is important to know how the ICMP packet is handled by Scapy:


11:53:58 dave ~>scapy
WARNING: No route found for IPv6 destination :: (no default route?)
Welcome to Scapy (2.1.0)
>>> ls(ICMP)
type : ByteEnumField = (8)
code : MultiEnumField = (0)
chksum : XShortField = (None)
id : ConditionalField = (0)
seq : ConditionalField = (0)
ts_ori : ConditionalField = (78842914)
ts_rx : ConditionalField = (78842914)
ts_tx : ConditionalField = (78842914)
gw : ConditionalField = ('0.0.0.0')
ptr : ConditionalField = (0)
reserved : ConditionalField = (0)
addr_mask : ConditionalField = ('0.0.0.0')
unused : ConditionalField = (0)
>>>

Let’s some snippets of code:


def send_echo_reply(self, pkt):
		ip = IP()
		icmp = ICMP()
		ip.src = pkt[IP].dst
		ip.dst = pkt[IP].src
		icmp.type = 0
		icmp.code = 0
		icmp.id = pkt[ICMP].id
		icmp.seq = pkt[ICMP].seq
		logger.info("Sending back an echo reply to %s" % ip.dst)
		data = pkt[ICMP].payload
		send(ip/icmp/data, verbose=0)

def process(i, payload):
	data = payload.get_data()
	pkt = IP(data)
	proto = pkt.proto

	# Check if it is a ICMP packet
	if proto is 0x01:
			logger.info("It's an ICMP packet")
			# Idea: intercept an echo request and immediately send back an echo reply packet
			if pkt[ICMP].type is 8:
				logger.info("It's an ICMP echo request packet")
				self.send_echo_reply(pkt)
			else:
				pass
....

Using Scapy we check if the packet is ICMP or not then we check if the ICMP packet is an echo request ( type 8 ) and in this case we invoke the function send_echo_reply. This function generates an echo reply but it is important to highlight some points. Obviously the id field of the ICMP packet should be the same one of the echo request and this is true for the seq field too. Another important feature to create a valid echo reply is that the payload of the reply is the same of the request, otherwise the trick will not work :) Last but least keep in mind the words of the Scapy’s FAQ:

In order to speak to local applications, you need to build your packets one layer upper, using a PF_INET/SOCK_RAW socket instead of a PF_PACKET/SOCK_RAW (or its equivalent on other systems than Linux):
>>> conf.L3socket
<class __main__.L3PacketSocket at 0xb7bdf5fc>
>>> conf.L3socket=L3RawSocket

Let’s run it and if it works as expected you should obtain something like that: 

(see the full code icmp.py)
The ICMP packets of the client have been pwned by our simple script now let’s try to fool the DNS. We know that before pinging a domainthe client try to resolve the name of that domain in order to have the IP address, basically this operation is a simple DNS request query. Once the query is successful the client will ping its target. DNS is a bit more complex than ICMP, let’s see DNS in Scapy:


>>> ls(DNS)
id : ShortField = (0)
qr : BitField = (0)
opcode : BitEnumField = (0)
aa : BitField = (0)
tc : BitField = (0)
rd : BitField = (0)
ra : BitField = (0)
z : BitField = (0)
rcode : BitEnumField = (0)
qdcount : DNSRRCountField = (None)
ancount : DNSRRCountField = (None)
nscount : DNSRRCountField = (None)
arcount : DNSRRCountField = (None)
qd : DNSQRField = (None)
an : DNSRRField = (None)
ns : DNSRRField = (None)
ar : DNSRRField = (None)
>>> ls(DNSQR)
qname : DNSStrField = ('')
qtype : ShortEnumField = (1)
qclass : ShortEnumField = (1)
>>> ls(DNSRR)
rrname : DNSStrField = ('')
type : ShortEnumField = (1)
rclass : ShortEnumField = (1)
ttl : IntField = (0)
rdlen : RDLenField = (None)
rdata : RDataField = ('')
>>>

In order to fully understand all the fields remember that google is your friend.
What we are going to do it is an idea quite simple. We are going to write a iptables rule for the DNS traffic and then we will deal with the packets in the NFQUEUE, basically DNS queries, then we will parse them and of course these packets will be dropped, we don’t want to contact external servers and we will generate the DNS response packet to trick the client. Let’s see the code:


def fake_dns_reply(self, pkt, qname):
 ip = IP()
 udp = UDP()
 ip.src = pkt[IP].dst
 ip.dst = pkt[IP].src
 udp.sport = pkt[UDP].dport
 udp.dport = pkt[UDP].sport

 solved_ip = "31.33.7.31" # I'm lazy, reader you might create a function to generate random IP :))
 qd = pkt[UDP].payload
 dns = DNS(id = qd.id, qr = 1, qdcount = 1, ancount = 1, arcount = 1, nscount = 1, rcode = 0)
 dns.qd = qd[DNSQR]
 dns.an = DNSRR(rrname = qname, ttl = 257540, rdlen = 4, rdata = solved_ip)
 dns.ns = DNSRR(rrname = qname, ttl = 257540, rdlen = 4, rdata = solved_ip)
 dns.ar = DNSRR(rrname = qname, ttl = 257540, rdlen = 4, rdata = solved_ip)
 print "Sending the fake DNS reply to %s:%s" % (ip.dst, udp.dport)
 send(ip/udp/dns)

This is the main function for generating a fake DNS reply. It is worth noting that the id field should be the same of the request, the ttl values come from looking at wireshark, they are common values in a DNS reply. Last but not least remember that the counters (qdcount, ancount etc) should contain the exact number of entries for the given section of the DNS packet. In the final code we have to handle the UDP protocol too (yes, DNS is over UDP) and in particular for our experiment we check the common port for  a DNS server, the UDP port number 53. Before going on you should remember to set the rule for the DNS traffic:

 iptables -A OUTPUT -p udp –dport 53 -j NFQUEUE

Finally if it works as expected:

(see the final code icmp_dns_fun.py). Keep in mind we can ping also a not available site and we will obtain anyway the DNS answer and obviously the ICMP echo replies.

Happy hacking!


emdel

About these ads

Tags: , , , , , ,

6 responses to “Having fun with NFQUEUE and Scapy”

  1. mr says :

    Hi. How can I do this in windows? I mean, does the windows firewall provides a queue?

    Thanks

  2. wilcoj says :

    Thanks this was a very useful and functional example of using NFQUEUE!

  3. Greg says :

    what version of python did you use? (And which way did you install nfqueue?)
    When I tried your code (or tried to make a similar one in my own) I’ve got:

    RuntimeError: error during nfq_unbind_pf()

    • 5d4a says :

      Hi Greg,
      The post is quite old (2 years ago), and maybe with the new version there’s something wrong.
      I didn’t check but it’s possible.
      The python version should be 2.7, but I have no idea for the nfqueue version.
      I don’t have the machine anymore but I installed the nfqueue bindings via apt on my Ubuntu.
      Let me know if you manage to run my scripts :)

      Happy hacking!

Trackbacks / Pingbacks

  1. Nebula level10 write-up | null pointer - 3 December 2011
  2. Having fun with NFQUEUE and Scapy | Hacking - A New Era - 17 January 2014

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: