Land #5840, add LLMNR & mDNS modules

bug/bundler_fix
HD Moore 2015-09-02 18:30:29 -05:00
commit 9f9bbce034
No known key found for this signature in database
GPG Key ID: 7549FB3DB1DD1F32
11 changed files with 377 additions and 12 deletions

View File

@ -0,0 +1,24 @@
# -*- coding: binary -*-
require 'msf/core/auxiliary/mdns'
module Msf
# This module provides methods for working with LLMNR
module Auxiliary::LLMNR
include Auxiliary::MDNS
# Initializes an instance of an auxiliary module that uses LLMNR
def initialize(info = {})
super
register_options(
[
OptAddressRange.new('RHOSTS', [true, 'The multicast address or CIDR range of targets to query', '224.0.0.252']),
Opt::RPORT(5355),
# TODO: allow more than one
OptString.new('NAME', [true, 'The name to query', 'localhost']),
OptString.new('TYPE', [true, 'The query type (name, # or TYPE#)', 'A'])
],
self.class
)
end
end
end

View File

@ -0,0 +1,108 @@
# -*- coding: binary -*-
require 'net/dns'
module Msf
# This module provides methods for working with mDNS
module Auxiliary::MDNS
# Initializes an instance of an auxiliary module that uses mDNS
def initialize(info = {})
super
register_options(
[
OptAddressRange.new('RHOSTS', [true, 'The multicast address or CIDR range of targets to query', '224.0.0.251']),
Opt::RPORT(5353),
OptString.new('NAME', [true, 'The name to query', '_services._dns-sd._udp.local']),
OptString.new('TYPE', [true, 'The query type (name, # or TYPE#)', 'PTR']),
OptString.new('CLASS', [true, 'The query class (name, # or CLASS#)', 'IN'])
],
self.class
)
end
def setup
query_class_name
query_type_name
end
def build_probe
@probe ||= ::Net::DNS::Packet.new(query_name, query_type_num, query_class_num).data
# TODO: support QU vs QM probes
# @probe[@probe.size-2] = [0x80].pack('C')
# @probe
end
def query_class
if datastore['CLASS'] =~ /^\d+$/
datastore['CLASS'].to_i
else
datastore['CLASS'].upcase
end
end
def query_class_name
Net::DNS::RR::Classes.new(query_class).to_s
end
def query_class_num
Net::DNS::RR::Classes.new(query_class).to_i
end
def query_type
if datastore['TYPE'] =~ /^\d+$/
datastore['TYPE'].to_i
else
datastore['TYPE'].upcase
end
end
def query_name
datastore['NAME']
end
def query_type_name
Net::DNS::RR::Types.new(query_type).to_s
end
def query_type_num
Net::DNS::RR::Types.new(query_type).to_i
end
def describe_response(response)
decoded = Resolv::DNS::Message.decode(response)
answers = decoded.answer
if answers.empty? # not sure this will ever happen...
"no answers"
else
# there are often many answers for the same RR, so group them
grouped_answers = answers.group_by { |name, _, _| name }
# now summarize each group by noting the resource type and the notable
# part(s) of that RR
summarized_answers = grouped_answers.map do |name, these_answers|
summarized_group = these_answers.map do |_, _, data|
case data
when Resolv::DNS::Resource::IN::A
"A #{data.address}"
when Resolv::DNS::Resource::IN::AAAA
"AAAA #{data.address}"
when Resolv::DNS::Resource::IN::PTR
"PTR #{data.name}"
when Resolv::DNS::Resource::IN::SRV
"SRV #{data.target}"
when Resolv::DNS::Resource::IN::TXT
"TXT #{data.strings.join(',')}"
else
data.inspect
end
end
"#{name}: (#{summarized_group.join(", ")})"
end
summarized_answers.join(', ')
end
end
def request_info
"#{query_name} #{query_class}/#{query_type}"
end
end
end

View File

@ -20,6 +20,8 @@ require 'msf/core/auxiliary/login'
require 'msf/core/auxiliary/rservices'
require 'msf/core/auxiliary/cisco'
require 'msf/core/auxiliary/kademlia'
require 'msf/core/auxiliary/llmnr'
require 'msf/core/auxiliary/mdns'
require 'msf/core/auxiliary/nmap'
require 'msf/core/auxiliary/natpmp'
require 'msf/core/auxiliary/iax2'

View File

@ -181,7 +181,9 @@ module Auxiliary::UDPScanner
end
# Called for each response packet
def scanner_process(data, shost, sport)
def scanner_process(data, shost, _sport)
@results[shost] ||= []
@results[shost] << data
end
# Called before the scan block

View File

@ -242,7 +242,7 @@ module Msf
dev ||= datastore['INTERFACE']
dst_mac, src_mac = lookup_eth(dhost, dev)
if dst_mac == nil and not bcast
return false
raise RuntimeError, 'Unable to determine the destination MAC and bcast is false'
end
inject_eth(:payload => payload, :eth_daddr => dst_mac, :eth_saddr => src_mac)
end

View File

@ -40,7 +40,7 @@ module Net # :nodoc:
when Fixnum
return Classes.invert.has_key?(cls)
else
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
raise ClassArgumentError, "Wrong class: #{cls.class}"
end
end
@ -55,7 +55,7 @@ module Net # :nodoc:
raise ClassArgumentError, "Unknown class number #{cls}"
end
else
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
raise ClassArgumentError, "Wrong class: #{cls.class}"
end
end
@ -81,7 +81,7 @@ module Net # :nodoc:
@str = Classes.invert[@@default]
@num = @@default
else
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
raise ClassArgumentError, "Wrong class: #{cls.class}"
end
end
@ -89,15 +89,15 @@ module Net # :nodoc:
# *PRIVATE* method
def new_from_string(cls)
case cls
when /^CLASS\\d+/
# TODO!!!
when /^CLASS(\d+)$/
new_from_num(Regexp.last_match(1).to_i)
else
# String with name of class
if Classes.has_key? cls
@str = cls
@num = Classes[cls]
else
raise ClassesArgumentError, "Unknown cls #{cls}"
raise ClassArgumentError, "Unknown class #{cls}"
end
end
end
@ -105,11 +105,13 @@ module Net # :nodoc:
# Contructor for numeric data class
# *PRIVATE* method
def new_from_num(cls)
raise ClassArgumentError, "Invalid class #{cls}" if cls < 0 || cls > 0xFFFF
if Classes.invert.has_key? cls
@num = cls
@str = Classes.invert[cls]
else
raise ClassesArgumentError, "Unknown cls number #{cls}"
@num = cls
@str = "CLASS#{cls}"
end
end

View File

@ -167,8 +167,8 @@ module Net # :nodoc:
# *PRIVATE* method
def new_from_string(type)
case type
when /^TYPE\\d+/
# TODO!!!
when /^TYPE(\d+)$/
new_from_num(Regexp.last_match(1).to_i)
else
# String with name of type
if Types.has_key? type
@ -183,11 +183,13 @@ module Net # :nodoc:
# Contructor for numeric data type
# *PRIVATE* method
def new_from_num(type)
raise TypeArgumentError, "Invalid type #{type}" if type < 0 || type > 0xFFFF
if Types.invert.has_key? type
@num = type
@str = Types.invert[type]
else
raise TypeArgumentError, "Unknown type number #{type}"
@num = type
@str = "TYPE#{type}"
end
end

View File

@ -0,0 +1,51 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Auxiliary::UDPScanner
include Msf::Auxiliary::LLMNR
def initialize(info = {})
super(
update_info(
info,
'Name' => 'LLMNR Query',
'Description' => %q(
This module sends LLMNR queries, which are really just normal UDP DNS
queries done (usually) over multicast on a different port, 5355.
Targets other than the default RHOSTS' 224.0.0.252 should not respond
but may anyway.
),
'Author' =>
[
'Jon Hart <jon_hart[at]rapid7.com>'
],
'License' => MSF_LICENSE
)
)
end
def scanner_prescan(batch)
print_status("Sending LLMNR #{query_type_name}/#{query_class_name} queries for #{query_name} to #{batch[0]}->#{batch[-1]} port #{rport} (#{batch.length} hosts)")
@results = {}
end
def scanner_postscan(_batch)
found = {}
@results.each_pair do |peer, resps|
resps.each do |resp|
found[peer] ||= {}
next if found[peer][resp]
response_info = describe_response(resp)
print_good("#{peer} responded with #{response_info}")
report_service(host: peer, port: rport, proto: "udp", name: "llmnr", info: response_info)
found[peer][resp] = true
end
end
end
end

View File

@ -0,0 +1,50 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Auxiliary::UDPScanner
include Msf::Auxiliary::MDNS
def initialize(info = {})
super(
update_info(
info,
'Name' => 'mDNS Query',
'Description' => %q(
This module sends mDNS queries, which are really just normal UDP DNS
queries done (usually) over multicast on a different port, 5353.
),
'Author' =>
[
'Jon Hart <jon_hart[at]rapid7.com>'
],
'License' => MSF_LICENSE
)
)
end
def scanner_prescan(batch)
print_status("Sending mDNS #{query_type_name} #{query_class_name} queries for " \
"#{query_name} to #{batch[0]}->#{batch[-1]} port #{rport} (#{batch.length} hosts)")
@results = {}
end
def scanner_postscan(_batch)
found = {}
@results.each_pair do |peer, resps|
resps.each do |resp|
found[peer] ||= {}
next if found[peer][resp]
response_info = describe_response(resp)
print_good("#{peer} responded with #{response_info}")
report_service(host: peer, port: rport, proto: "udp", name: "mdns", info: response_info)
found[peer][resp] = true
end
end
end
end

View File

@ -0,0 +1,62 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'net/dns'
describe Net::DNS::RR::Classes do
subject do
described_class.new
end
describe '#initialize' do
subject(:rr_class) do
described_class.allocate
end
it 'raises when initialized with no args' do
expect { rr_class.send(:initialize) }.to raise_error(ArgumentError)
end
it 'respects default RR class when initialized with a nil RR class' do
rr_class.send(:initialize, nil)
expect(rr_class.to_i).to eql(1)
expect(rr_class.to_s).to eql('IN')
end
# TODO: figure out why this doesn't work
skip 'respects configured default RR class' do
rr_class.send(:default=, 'NONE')
expect(rr_class.to_i).to eql(254)
expect(rr_class.to_s).to eql('NONE')
end
it 'initializes with a valid RR class Fixnum argument' do
rr_class.send(:initialize, 4)
expect(rr_class.to_i).to eql(4)
expect(rr_class.to_s).to eql('HS')
end
it 'raises when the supplied RR class Fixnum is invalid' do
expect { rr_class.send(:initialize, 123456) }.to raise_error(ClassArgumentError)
expect { rr_class.send(:initialize, -1) }.to raise_error(ClassArgumentError)
end
it 'initializes with a valid RR class String argument' do
rr_class.send(:initialize, 'CH')
expect(rr_class.to_i).to eql(3)
expect(rr_class.to_s).to eql('CH')
rr_class.send(:initialize, 'CLASS9')
expect(rr_class.to_i).to eql(9)
expect(rr_class.to_s).to eql('CLASS9')
rr_class.send(:initialize, 'CLASS1')
expect(rr_class.to_i).to eql(1)
expect(rr_class.to_s).to eql('IN')
end
it 'raises when the supplied RR class String is invalid' do
expect { rr_class.send(:initialize, 'cats') }.to raise_error(ClassArgumentError)
expect { rr_class.send(:initialize, 'CLASS123456') }.to raise_error(ClassArgumentError)
end
end
end

View File

@ -0,0 +1,62 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'net/dns'
describe Net::DNS::RR::Types do
subject do
described_class.new
end
describe '#initialize' do
subject(:rr_type) do
described_class.allocate
end
it 'raises when initialized with no args' do
expect { rr_type.send(:initialize) }.to raise_error(ArgumentError)
end
it 'respects default RR type when initialized with a nil RR type' do
rr_type.send(:initialize, nil)
expect(rr_type.to_i).to eql(1)
expect(rr_type.to_s).to eql('A')
end
# TODO: figure out why this doesn't work
skip 'respects configured default RR type' do
rr_type.send(:default=, 'CNAME')
expect(rr_type.to_i).to eql(5)
expect(rr_type.to_s).to eql('CNAME')
end
it 'initializes with a valid RR type Fixnum argument' do
rr_type.send(:initialize, 2)
expect(rr_type.to_i).to eql(2)
expect(rr_type.to_s).to eql('NS')
end
it 'raises when the supplied RR type Fixnum is invalid' do
expect { rr_type.send(:initialize, 123456) }.to raise_error(TypeArgumentError)
expect { rr_type.send(:initialize, -1) }.to raise_error(TypeArgumentError)
end
it 'initializes with a valid RR type String argument' do
rr_type.send(:initialize, 'SRV')
expect(rr_type.to_i).to eql(33)
expect(rr_type.to_s).to eql('SRV')
rr_type.send(:initialize, 'TYPE12')
expect(rr_type.to_i).to eql(12)
expect(rr_type.to_s).to eql('PTR')
rr_type.send(:initialize, 'TYPE123')
expect(rr_type.to_i).to eql(123)
expect(rr_type.to_s).to eql('TYPE123')
end
it 'raises when the supplied RR type String is invalid' do
expect { rr_type.send(:initialize, 'cats') }.to raise_error(TypeArgumentError)
expect { rr_type.send(:initialize, 'TYPE123456') }.to raise_error(TypeArgumentError)
end
end
end