Land #6611, add native DNS to Rex, MSF mixin, sample modules
parent
5ec3da843e
commit
d6beb94c59
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This namespace exposes methods for interacting with and providing services
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::DNS
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'msf/core/exploit/dns/common'
|
||||
require 'msf/core/exploit/dns/client'
|
||||
require 'msf/core/exploit/dns/server'
|
|
@ -0,0 +1,217 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module exposes methods for querying a remote DNS service
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::DNS
|
||||
module Client
|
||||
|
||||
include Common
|
||||
include Exploit::Remote::Udp
|
||||
include Exploit::Remote::Tcp
|
||||
|
||||
#
|
||||
# Initializes an exploit module that interacts with a DNS server.
|
||||
#
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
deregister_options('RHOST')
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(53),
|
||||
Opt::Proxies,
|
||||
OptString.new('DOMAIN', [ false, "The target domain name"]),
|
||||
OptString.new('NS', [ false, "Specify the nameservers to use for queries, space separated" ]),
|
||||
OptString.new('SEARCHLIST', [ false, "DNS domain search list, comma separated"]),
|
||||
OptInt.new('THREADS', [true, "Number of threads to use in threaded queries", 1])
|
||||
], Exploit::Remote::DNS::Client
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('DnsClientDefaultNS', [ false, "Specify the default to use for queries, space separated", '8.8.8.8 8.8.4.4' ]),
|
||||
OptInt.new('DnsClientRetry', [ false, "Number of times to try to resolve a record if no response is received", 2]),
|
||||
OptInt.new('DnsClientRetryInterval', [ false, "Number of seconds to wait before doing a retry", 2]),
|
||||
OptBool.new('DnsClientReportARecords', [false, "Add hosts found via BRT and RVL to DB", true]),
|
||||
OptBool.new('DnsClientRVLExistingOnly', [false, "Only perform lookups on hosts in DB", true]),
|
||||
OptBool.new('DnsClientTcpDns', [false, "Run queries over TCP", false]),
|
||||
OptPath.new('DnsClientResolvconf', [true, "Resolvconf formatted configuration file to use for Resolver", "/dev/null"])
|
||||
], Exploit::Remote::DNS::Client
|
||||
)
|
||||
|
||||
register_autofilter_ports([ 53 ]) if respond_to?(:register_autofilter_ports)
|
||||
register_autofilter_services(%W{ dns }) if respond_to?(:register_autofilter_services)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Convenience wrapper around Resolver's query method - send DNS request
|
||||
#
|
||||
# @param domain [String] Domain for which to request a record
|
||||
# @param type [String] Type of record to request for domain
|
||||
#
|
||||
# @return [Dnsruby::RR] DNS response
|
||||
def query(domain = datastore['DOMAIN'], type = 'A')
|
||||
client.query(domain, type)
|
||||
end
|
||||
|
||||
#
|
||||
# Performs a set of asynchronous lookups for an array of domain,type pairs
|
||||
#
|
||||
# @param queries [Array] Set of domain,type pairs to pass into #query
|
||||
# @param threadmax [Fixnum] Max number of running threads at a time
|
||||
# @param block [Proc] Code block to execute with the query result
|
||||
#
|
||||
# @return [Array] Resulting set of responses or responses processed by passed blocks
|
||||
def query_async(queries = [], threadmax = datastore['THREADS'], &block)
|
||||
running = []
|
||||
while !queries.empty?
|
||||
domain, type = queries.shift
|
||||
running << framework.threads.spawn("Module(#{self.refname})-#{domain} #{type}", false) do |qat|
|
||||
if block
|
||||
block.call(query(domain,type))
|
||||
else
|
||||
query(domain,type)
|
||||
end
|
||||
end
|
||||
while running.select(&:alive?).count >= threadmax
|
||||
Rex::ThreadSafe.sleep(1)
|
||||
end
|
||||
end
|
||||
return running.join
|
||||
end
|
||||
|
||||
#
|
||||
# Switch DNS forwarders in resolver with thread safety
|
||||
#
|
||||
# @param ns [Array, String] List of (or single) nameservers to use
|
||||
def set_nameserver(ns = [])
|
||||
if ns.respond_to?(:split)
|
||||
ns = [ns]
|
||||
end
|
||||
@lock.synchronize do
|
||||
@dns_resolver.nameserver = ns.flatten
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Switch nameservers to use explicit NS or SOA for target
|
||||
#
|
||||
# @param domain [String] Domain for which to find SOA
|
||||
def switchdns(domain)
|
||||
if datastore['NS'].blank?
|
||||
resp_soa = client.query(target, "SOA")
|
||||
if (resp_soa)
|
||||
(resp_soa.answer.select { |i| i.is_a?(Dnsruby::RR::SOA)}).each do |rr|
|
||||
resp_1_soa = client.search(rr.mname)
|
||||
if (resp_1_soa and resp_1_soa.answer[0])
|
||||
set_nameserver(resp_1_soa.answer.map(&:address).compact.map(&:to_s))
|
||||
print_status("Set DNS Server to #{target} NS: #{client.nameserver.join(', ')}")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
vprint_status("Using DNS Server: #{client.nameserver.join(', ')}")
|
||||
client.nameserver = process_nameservers
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Detect if target has wildcards enabled for a record type
|
||||
#
|
||||
# @param target [String] Domain to test
|
||||
# @param type [String] Record type to test
|
||||
#
|
||||
# @return [String] Address which is returned for wildcard requests
|
||||
def wildcard(domain, type = "A")
|
||||
addr = false
|
||||
rendsub = rand(10000).to_s
|
||||
response = query("#{rendsub}.#{target}", type)
|
||||
if response.answer.length != 0
|
||||
vprint_status("This domain has wildcards enabled!!")
|
||||
response.answer.each do |rr|
|
||||
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Dnsruby::RR::CNAME
|
||||
addr = rr.address.to_s
|
||||
end
|
||||
end
|
||||
return addr
|
||||
end
|
||||
|
||||
#
|
||||
# Create and configure Resolver object
|
||||
#
|
||||
def setup_resolver
|
||||
options.validate(datastore) # This is a hack, DS values should not be Strings prior to this
|
||||
config = {
|
||||
:config_file => datastore['DnsClientResolvconf'],
|
||||
:nameservers => process_nameservers,
|
||||
:port => datastore['RPORT'],
|
||||
:retry_number => datastore['DnsClientRetry'].to_i,
|
||||
:retry_interval => datastore['DnsClientRetryInterval'].to_i,
|
||||
:use_tcp => datastore['DnsClientTcpDns'],
|
||||
:context => {'Msf' => framework, 'MsfExploit' => self}
|
||||
}
|
||||
if datastore['SEARCHLIST']
|
||||
if datastore['SEARCHLIST'].split(',').all? do |search|
|
||||
search.match(MATCH_HOSTNAME)
|
||||
end
|
||||
config[:search_list] = datastore['SEARCHLIST'].split(',')
|
||||
else
|
||||
raise 'Domain search list must consist of valid domains'
|
||||
end
|
||||
end
|
||||
if datastore['CHOST']
|
||||
config[:source_address] = IPAddr.new(datastore['CHOST'].to_s)
|
||||
end
|
||||
if datastore['CPORT']
|
||||
config[:source_port] = datastore['CPORT'] unless datastore['CPORT'] == 0
|
||||
end
|
||||
if datastore['Proxies']
|
||||
vprint_status("Using DNS/TCP resolution for proxy config")
|
||||
config[:use_tcp] = true
|
||||
config[:proxies] = datastore['Proxies']
|
||||
end
|
||||
@dns_resolver_lock = Mutex.new unless @dns_resolver_lock
|
||||
@dns_resolver = Rex::Proto::DNS::Resolver.new(config)
|
||||
end
|
||||
|
||||
#
|
||||
# Convenience method for DNS resolver as client
|
||||
# Executes setup_resolver if none exists
|
||||
#
|
||||
def client
|
||||
setup_resolver unless @dns_resolver
|
||||
@dns_resolver
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the resolver's nameservers
|
||||
# Uses explicitly defined NS option if set
|
||||
# Uses RHOSTS if not explicitly defined
|
||||
def process_nameservers
|
||||
if datastore['NS'].blank?
|
||||
nameservers = datastore['DnsClientDefaultNS'].split(/\s|,/)
|
||||
else
|
||||
nameservers = datastore['NS'].split(/\s|,/)
|
||||
end
|
||||
|
||||
invalid = nameservers.select { |ns| !Rex::Socket.dotted_ip?(ns) }
|
||||
if !invalid.empty?
|
||||
raise "Nameservers must be IP addresses. The following were invalid: #{invalid.join(", ")}"
|
||||
end
|
||||
|
||||
nameservers
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module exposes methods for querying a remote DNS service
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::DNS
|
||||
module Common
|
||||
|
||||
MATCH_HOSTNAME = Rex::Proto::DNS::Constants::MATCH_HOSTNAME
|
||||
|
||||
Packet = Rex::Proto::DNS::Packet
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,163 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dns'
|
||||
require 'msf/core/exploit/dns/common'
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module exposes methods for querying a remote DNS service
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::DNS
|
||||
module Server
|
||||
include Exploit::Remote::DNS::Common
|
||||
include Exploit::Remote::SocketServer
|
||||
|
||||
#
|
||||
# Initializes an exploit module that serves DNS requests
|
||||
#
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [true, 'The local port to listen on.', 53]),
|
||||
OptString.new('STATIC_ENTRIES', [ false, "DNS domain search list (hosts file or space/semicolon separate entries)"]),
|
||||
OptBool.new('DISABLE_RESOLVER', [ false, "Disable DNS request forwarding", false]),
|
||||
OptBool.new('DISABLE_NS_CACHE', [ false, "Disable DNS response caching", false])
|
||||
], Exploit::Remote::DNS::Server
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('DnsServerUdp', [true, "Serve UDP DNS requests", true]),
|
||||
OptBool.new('DnsServerTcp', [true, "Serve TCP DNS requests", false])
|
||||
], Exploit::Remote::DNS::Server
|
||||
)
|
||||
end
|
||||
|
||||
attr_accessor :service # :nodoc:
|
||||
|
||||
#
|
||||
# Process static entries
|
||||
#
|
||||
# @param entries [String] Filename or String containing static entries
|
||||
# @param type [String] Type of record for which to add static entries
|
||||
#
|
||||
# @return [Array] List of static entries in the cache
|
||||
def add_static_hosts(entries = datastore['STATIC_ENTRIES'], type = 'A')
|
||||
return if entries.nil? or entries.empty?
|
||||
if File.file?(File.expand_path(entries))
|
||||
data = File.read(File.expand_path(entries)).split("\n")
|
||||
else
|
||||
data = entries.split(';')
|
||||
end
|
||||
data.each do |entry|
|
||||
next if entry.gsub(/\s/,'').empty?
|
||||
addr, names = entry.split(' ', 2)
|
||||
names.split.each do |name|
|
||||
name << '.' unless name[-1] == '.' or name == '*'
|
||||
service.cache.add_static(name, addr, type)
|
||||
end
|
||||
end
|
||||
service.cache.records.select {|r,e| e == 0}
|
||||
end
|
||||
|
||||
#
|
||||
# Flush all static entries
|
||||
#
|
||||
def flush_static_hosts
|
||||
data.cache.records.select {|r,e| e == 0}.each do |flush|
|
||||
data.cache.delete(flush)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Flush cache entries
|
||||
# @param static [TrueClass, FalseClass] flush static hosts
|
||||
def flush_cache(static = false)
|
||||
self.service.cache.stop(true)
|
||||
flush_static_hosts if static
|
||||
self.service.cache.start
|
||||
end
|
||||
|
||||
#
|
||||
# Handle incoming requests
|
||||
# Override this method in modules to take flow control
|
||||
#
|
||||
def on_dispatch_request(cli, data)
|
||||
service.default_dispatch_request(cli,data)
|
||||
end
|
||||
|
||||
#
|
||||
# Handle incoming requests
|
||||
# Override this method in modules to take flow control
|
||||
#
|
||||
def on_send_response(cli, data)
|
||||
cli.write(data)
|
||||
end
|
||||
|
||||
#
|
||||
# Starts the server
|
||||
#
|
||||
def start_service
|
||||
begin
|
||||
|
||||
comm = _determine_server_comm
|
||||
self.service = Rex::ServiceManager.start(
|
||||
Rex::Proto::DNS::Server,
|
||||
datastore['SRVHOST'],
|
||||
datastore['SRVPORT'],
|
||||
datastore['DnsServerUdp'],
|
||||
datastore['DnsServerTcp'],
|
||||
(use_resolver? ? setup_resolver : false),
|
||||
comm,
|
||||
{'Msf' => framework, 'MsfExploit' => self}
|
||||
)
|
||||
|
||||
self.service.dispatch_request_proc = Proc.new do |cli, data|
|
||||
on_dispatch_request(cli,data)
|
||||
end
|
||||
self.service.send_response_proc = Proc.new do |cli, data|
|
||||
on_send_response(cli,data)
|
||||
end
|
||||
|
||||
add_static_hosts
|
||||
self.service.start(!datastore['DISABLE_NS_CACHE'])
|
||||
|
||||
rescue ::Errno::EACCES => e
|
||||
raise Rex::BindFailed.new(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Stops the server
|
||||
# @param destroy [TrueClass,FalseClass] Dereference the server object
|
||||
def stop_service(destroy = false)
|
||||
Rex::ServiceManager.stop_service(self.service) if self.service
|
||||
if destroy
|
||||
@dns_resolver = nil if @dns_resolver
|
||||
self.service = nil if self.service
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Resets the DNS server
|
||||
#
|
||||
def reset_service
|
||||
stop_service(true)
|
||||
start_service
|
||||
end
|
||||
|
||||
#
|
||||
# Determines if resolver is available and configured for use
|
||||
#
|
||||
def use_resolver?
|
||||
!datastore['DISABLE_RESOLVER'] and self.respond_to?(:setup_resolver)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,172 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This mixin provides a generic interface for running a socket server of some
|
||||
# sort that is designed to exploit clients. Exploits that include this mixin
|
||||
# automatically take a passive stance.
|
||||
#
|
||||
###
|
||||
|
||||
module Exploit::Remote::SocketServer
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Stance' => Msf::Exploit::Stance::Passive))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptAddress.new('SRVHOST', [ true, "The local host to listen on. This must be an address on the local machine or 0.0.0.0", '0.0.0.0' ]),
|
||||
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 8080 ]),
|
||||
|
||||
], Msf::Exploit::Remote::SocketServer
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('ListenerComm', [ false, 'The specific communication channel to use for this service'])
|
||||
], Msf::Exploit::Remote::SocketServer
|
||||
)
|
||||
end
|
||||
|
||||
#
|
||||
# This mixin overrides the exploit method so that it can initiate the
|
||||
# service that corresponds with what the client has requested.
|
||||
#
|
||||
def exploit
|
||||
|
||||
start_service()
|
||||
print_status("Server started.")
|
||||
|
||||
# Call the exploit primer
|
||||
primer
|
||||
|
||||
# Wait on the service to stop
|
||||
self.service.wait
|
||||
end
|
||||
|
||||
#
|
||||
# Primer method to call after starting service but before handling connections
|
||||
#
|
||||
def primer
|
||||
end
|
||||
|
||||
#
|
||||
# Stops the service, if one was created.
|
||||
#
|
||||
def cleanup
|
||||
super
|
||||
if(service)
|
||||
stop_service()
|
||||
print_status("Server stopped.")
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Called when a client has data available for reading.
|
||||
#
|
||||
def on_client_data(client)
|
||||
end
|
||||
|
||||
#
|
||||
# Starts the service. Override this method in consumers
|
||||
#
|
||||
def start_service(*args)
|
||||
end
|
||||
|
||||
#
|
||||
# Stops the service.
|
||||
#
|
||||
def stop_service
|
||||
if (service)
|
||||
begin
|
||||
self.service.deref if self.service.kind_of?(Rex::Service)
|
||||
if self.service.kind_of?(Rex::Socket)
|
||||
self.service.close
|
||||
self.service.stop
|
||||
end
|
||||
|
||||
self.service = nil
|
||||
rescue ::Exception
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the local host that is being listened on.
|
||||
#
|
||||
def srvhost
|
||||
datastore['SRVHOST']
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the local port that is being listened on.
|
||||
#
|
||||
def srvport
|
||||
datastore['SRVPORT']
|
||||
end
|
||||
|
||||
#
|
||||
# Re-generates the payload, substituting the current RHOST and RPORT with
|
||||
# the supplied client host and port from the socket.
|
||||
#
|
||||
def regenerate_payload(cli, arch = nil, platform = nil, target = nil)
|
||||
|
||||
ohost = datastore['RHOST']
|
||||
oport = datastore['RPORT']
|
||||
p = nil
|
||||
|
||||
begin
|
||||
# Update the datastore with the supplied client peerhost/peerport
|
||||
datastore['RHOST'] = cli.peerhost
|
||||
datastore['RPORT'] = cli.peerport
|
||||
|
||||
if ((p = super(arch, platform, target)) == nil)
|
||||
print_error("Failed to generate payload")
|
||||
return nil
|
||||
end
|
||||
|
||||
# Allow the payload to start a new handler
|
||||
add_handler({
|
||||
'RHOST' => datastore['RHOST'],
|
||||
'RPORT' => datastore['RPORT']
|
||||
})
|
||||
|
||||
ensure
|
||||
datastore['RHOST'] = ohost
|
||||
datastore['RPORT'] = oport
|
||||
end
|
||||
|
||||
p
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# Determines appropriate listener comm
|
||||
#
|
||||
def _determine_server_comm(srv_comm = datastore['ListenerComm'].to_s)
|
||||
case srv_comm
|
||||
when 'local'
|
||||
comm = ::Rex::Socket::Comm::Local
|
||||
when /\A[0-9]+\Z/
|
||||
comm = framework.sessions[srv_comm.to_i]
|
||||
raise(RuntimeError, "Socket Server Comm (Session #{srv_comm}) does not exist") unless comm
|
||||
raise(RuntimeError, "Socket Server Comm (Session #{srv_comm}) does not implement Rex::Socket::Comm") unless comm.is_a? ::Rex::Socket::Comm
|
||||
when nil, ''
|
||||
comm = nil
|
||||
else
|
||||
raise(RuntimeError, "SocketServer Comm '#{srv_comm}' is invalid")
|
||||
end
|
||||
|
||||
comm
|
||||
end
|
||||
|
||||
attr_accessor :service # :nodoc:
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'msf/core/exploit/socket_server'
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
|
@ -10,20 +12,18 @@ module Msf
|
|||
#
|
||||
###
|
||||
module Exploit::Remote::TcpServer
|
||||
include Exploit::Remote::SocketServer
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Stance' => Msf::Exploit::Stance::Passive))
|
||||
super
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptBool.new('SSL', [ false, 'Negotiate SSL for incoming connections', false]),
|
||||
# SSLVersion is currently unsupported for TCP servers (only supported by clients at the moment)
|
||||
OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']),
|
||||
OptAddress.new('SRVHOST', [ true, "The local host to listen on. This must be an address on the local machine or 0.0.0.0", '0.0.0.0' ]),
|
||||
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 8080 ]),
|
||||
|
||||
], Msf::Exploit::Remote::TcpServer)
|
||||
OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)'])
|
||||
], Msf::Exploit::Remote::TcpServer
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
|
@ -40,51 +40,11 @@ module Exploit::Remote::TcpServer
|
|||
)
|
||||
end
|
||||
|
||||
#
|
||||
# This mixin overrides the exploit method so that it can initiate the
|
||||
# service that corresponds with what the client has requested.
|
||||
#
|
||||
def exploit
|
||||
|
||||
start_service()
|
||||
print_status("Server started.")
|
||||
|
||||
# Call the exploit primer
|
||||
primer
|
||||
|
||||
# Wait on the service to stop
|
||||
self.service.wait
|
||||
end
|
||||
|
||||
#
|
||||
# Primer method to call after starting service but before handling connections
|
||||
#
|
||||
def primer
|
||||
end
|
||||
|
||||
#
|
||||
# Stops the service, if one was created.
|
||||
#
|
||||
def cleanup
|
||||
super
|
||||
if(service)
|
||||
stop_service()
|
||||
print_status("Server stopped.")
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Called when a client connects.
|
||||
#
|
||||
def on_client_connect(client)
|
||||
end
|
||||
|
||||
#
|
||||
# Called when a client has data available for reading.
|
||||
#
|
||||
def on_client_data(client)
|
||||
end
|
||||
|
||||
#
|
||||
# Called when a client has disconnected.
|
||||
#
|
||||
|
@ -97,12 +57,7 @@ module Exploit::Remote::TcpServer
|
|||
def start_service(*args)
|
||||
begin
|
||||
|
||||
comm = datastore['ListenerComm']
|
||||
if comm == "local"
|
||||
comm = ::Rex::Socket::Comm::Local
|
||||
else
|
||||
comm = nil
|
||||
end
|
||||
comm = _determine_server_comm
|
||||
|
||||
self.service = Rex::Socket::TcpServer.create(
|
||||
'LocalHost' => srvhost,
|
||||
|
@ -151,38 +106,6 @@ module Exploit::Remote::TcpServer
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Stops the service.
|
||||
#
|
||||
def stop_service
|
||||
if (service)
|
||||
begin
|
||||
self.service.deref if self.service.kind_of?(Rex::Service)
|
||||
if self.service.kind_of?(Rex::Socket)
|
||||
self.service.close
|
||||
self.service.stop
|
||||
end
|
||||
|
||||
self.service = nil
|
||||
rescue ::Exception
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the local host that is being listened on.
|
||||
#
|
||||
def srvhost
|
||||
datastore['SRVHOST']
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the local port that is being listened on.
|
||||
#
|
||||
def srvport
|
||||
datastore['SRVPORT']
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the SSL option
|
||||
#
|
||||
|
@ -209,44 +132,6 @@ module Exploit::Remote::TcpServer
|
|||
datastore['SSLCompression']
|
||||
end
|
||||
|
||||
#
|
||||
# Re-generates the payload, substituting the current RHOST and RPORT with
|
||||
# the supplied client host and port from the socket.
|
||||
#
|
||||
def regenerate_payload(cli, arch = nil, platform = nil, target = nil)
|
||||
|
||||
ohost = datastore['RHOST']
|
||||
oport = datastore['RPORT']
|
||||
p = nil
|
||||
|
||||
begin
|
||||
# Update the datastore with the supplied client peerhost/peerport
|
||||
datastore['RHOST'] = cli.peerhost
|
||||
datastore['RPORT'] = cli.peerport
|
||||
|
||||
if ((p = super(arch, platform, target)) == nil)
|
||||
print_error("Failed to generate payload")
|
||||
return nil
|
||||
end
|
||||
|
||||
# Allow the payload to start a new handler
|
||||
add_handler({
|
||||
'RHOST' => datastore['RHOST'],
|
||||
'RPORT' => datastore['RPORT']
|
||||
})
|
||||
|
||||
ensure
|
||||
datastore['RHOST'] = ohost
|
||||
datastore['RPORT'] = oport
|
||||
end
|
||||
|
||||
p
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :service # :nodoc:
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -184,6 +184,7 @@ module Net # :nodoc:
|
|||
nscount += 1
|
||||
end
|
||||
@additional.each do |rr|
|
||||
next if rr.nil?
|
||||
data += rr.data#(data.length)
|
||||
arcount += 1
|
||||
end
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'thread'
|
||||
|
||||
module Rex
|
||||
module IO
|
||||
|
||||
###
|
||||
#
|
||||
# This mixin provides the framework and interface for implementing a datagram
|
||||
# server that can handle incoming datagrams. Datagram servers include this mixin
|
||||
#
|
||||
###
|
||||
module GramServer
|
||||
|
||||
##
|
||||
#
|
||||
# Abstract methods
|
||||
#
|
||||
##
|
||||
|
||||
##
|
||||
#
|
||||
# Default server monitoring and client management implementation follows
|
||||
# below.
|
||||
#
|
||||
##
|
||||
|
||||
|
||||
#
|
||||
# This callback is notified when a client connection has data that needs to
|
||||
# be processed.
|
||||
#
|
||||
def dispatch_request(client, data)
|
||||
if (dispatch_request_proc)
|
||||
dispatch_request_proc.call(client, data)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This callback is notified when data must be returned to the client
|
||||
# @param client [Socket] Client/Socket to receive data
|
||||
# @param data [String] Data to be sent to client/socket
|
||||
def send_response(client, data)
|
||||
if (send_response_proc)
|
||||
send_response_proc.call(client, data)
|
||||
else
|
||||
client.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Start monitoring the listener socket for connections and keep track of
|
||||
# all client connections.
|
||||
#
|
||||
def start
|
||||
self.listener_thread = Rex::ThreadFactory.spawn("GramServerListener", false) {
|
||||
monitor_listener
|
||||
}
|
||||
end
|
||||
|
||||
#
|
||||
# Terminates the listener monitoring threads and closes all active clients.
|
||||
#
|
||||
def stop
|
||||
self.listener_thread.kill
|
||||
end
|
||||
|
||||
#
|
||||
# This method waits on the server listener thread
|
||||
#
|
||||
def wait
|
||||
self.listener_thread.join if self.listener_thread
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Callback procedures.
|
||||
#
|
||||
##
|
||||
|
||||
#
|
||||
# This callback procedure can be set and will be called when clients
|
||||
# have data to be processed.
|
||||
#
|
||||
attr_accessor :dispatch_request_proc, :send_response_proc
|
||||
|
||||
attr_accessor :listener_thread# :nodoc:
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -250,6 +250,13 @@ class Channel
|
|||
written.nil? ? 0 : written.value
|
||||
end
|
||||
|
||||
#
|
||||
# Wrapper around check for self.cid
|
||||
#
|
||||
def closed?
|
||||
self.cid.nil?
|
||||
end
|
||||
|
||||
#
|
||||
# Wrapper around the low-level close.
|
||||
#
|
||||
|
|
|
@ -9,6 +9,7 @@ require 'rex/proto/kerberos'
|
|||
require 'rex/proto/rmi'
|
||||
require 'rex/proto/sms'
|
||||
require 'rex/proto/mms'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module DNS
|
||||
|
||||
module Constants
|
||||
MATCH_HOSTNAME=/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]\.*)$/
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/proto/dns/packet'
|
||||
require 'rex/proto/dns/resolver'
|
||||
require 'rex/proto/dns/server'
|
|
@ -0,0 +1,315 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'net/dns'
|
||||
require 'resolv'
|
||||
require 'dnsruby'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module DNS
|
||||
|
||||
module Packet
|
||||
|
||||
#
|
||||
# Checks string to ensure it can be used as a valid hostname
|
||||
#
|
||||
# @param subject [String] Subject name to check
|
||||
#
|
||||
# @return [TrueClass,FalseClass] Disposition on name match
|
||||
def self.valid_hostname?(subject = '')
|
||||
!subject.match(Rex::Proto::DNS::Constants::MATCH_HOSTNAME).nil?
|
||||
end
|
||||
|
||||
#
|
||||
# Reconstructs a packet with both standard DNS libraries
|
||||
# Ensures that headers match the payload
|
||||
#
|
||||
# @param packet [String, Net::DNS::Packet, Dnsruby::Message] Data to be validated
|
||||
#
|
||||
# @return [Dnsruby::Message]
|
||||
def self.validate(packet)
|
||||
self.encode_drb(self.encode_net(self.encode_res(packet)))
|
||||
end
|
||||
|
||||
#
|
||||
# Sets header values to match packet content
|
||||
#
|
||||
# @param packet [String] Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message]
|
||||
#
|
||||
# @return [Dnsruby::Message]
|
||||
def self.recalc_headers(packet)
|
||||
packet = self.encode_drb(packet)
|
||||
{
|
||||
:qdcount= => :question,
|
||||
:ancount= => :answer,
|
||||
:nscount= => :authority,
|
||||
:arcount= => :additional
|
||||
}.each do |header,body|
|
||||
packet.header.send(header,packet.send(body).count)
|
||||
end
|
||||
|
||||
return packet
|
||||
end
|
||||
|
||||
#
|
||||
# Reads a packet into the Net::DNS::Packet format
|
||||
#
|
||||
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
|
||||
#
|
||||
# @return [Net::DNS::Packet]
|
||||
def self.encode_net(packet)
|
||||
return packet if packet.is_a?(Net::DNS::Packet)
|
||||
Net::DNS::Packet.parse(
|
||||
self.encode_raw(packet)
|
||||
)
|
||||
end
|
||||
|
||||
# Reads a packet into the Resolv::DNS::Message format
|
||||
#
|
||||
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
|
||||
#
|
||||
# @return [Resolv::DNS::Message]
|
||||
def self.encode_res(packet)
|
||||
return packet if packet.is_a?(Resolv::DNS::Message)
|
||||
Resolv::DNS::Message.decode(
|
||||
self.encode_raw(packet)
|
||||
)
|
||||
end
|
||||
|
||||
# Reads a packet into the Dnsruby::Message format
|
||||
#
|
||||
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
|
||||
#
|
||||
# @return [Dnsruby::Message]
|
||||
def self.encode_drb(packet)
|
||||
return packet if packet.is_a?(Dnsruby::Message)
|
||||
Dnsruby::Message.decode(
|
||||
self.encode_raw(packet)
|
||||
)
|
||||
end
|
||||
|
||||
# Reads a packet into the raw String format
|
||||
#
|
||||
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
|
||||
#
|
||||
# @return [String]
|
||||
def self.encode_raw(packet)
|
||||
return packet unless packet.respond_to?(:encode) or packet.respond_to?(:data)
|
||||
(packet.respond_to?(:data) ? packet.data : packet.encode).force_encoding('binary')
|
||||
end
|
||||
|
||||
#
|
||||
# Generates a request packet, taken from Net::DNS::Resolver
|
||||
#
|
||||
# @param subject [String] Subject name of question section
|
||||
# @param type [Fixnum] Type of DNS record to query
|
||||
# @param cls [Fixnum] Class of dns record to query
|
||||
# @param recurse [Fixnum] Recursive query or not
|
||||
#
|
||||
# @return [Dnsruby::Message] request packet
|
||||
def self.generate_request(subject, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN, recurse = 1)
|
||||
case subject
|
||||
when IPAddr
|
||||
name = subject.reverse
|
||||
type = Dnsruby::Types::PTR
|
||||
when /\d/ # Contains a number, try to see if it's an IP or IPv6 address
|
||||
begin
|
||||
name = IPAddr.new(subject).reverse
|
||||
type = Dnsruby::Types::PTR
|
||||
rescue ArgumentError
|
||||
name = subject if self.valid_hostname?(subject)
|
||||
end
|
||||
else
|
||||
name = subject if self.valid_hostname?(subject)
|
||||
end
|
||||
|
||||
# Create the packet
|
||||
packet = Dnsruby::Message.new(name, type, cls)
|
||||
|
||||
if packet.header.opcode == Dnsruby::OpCode::Query
|
||||
packet.header.recursive = recurse
|
||||
end
|
||||
|
||||
# DNSSEC and TSIG stuff to be inserted here
|
||||
|
||||
return packet
|
||||
end
|
||||
|
||||
#
|
||||
# Generates a response packet for an existing request
|
||||
#
|
||||
# @param request [String] Net::DNS::Packet, Resolv::DNS::Message] Original request
|
||||
# @param answer [Array] Set of answers to provide in the response
|
||||
# @param authority [Array] Set of authority records to provide in the response
|
||||
# @param additional [Array] Set of additional records to provide in the response
|
||||
#
|
||||
# @return [Dnsruby::Message] Response packet
|
||||
def self.generate_response(request, answer = nil, authority = nil, additional = nil)
|
||||
packet = self.encode_drb(request)
|
||||
packet.answer = answer if answer
|
||||
packet.authority = authority if authority
|
||||
packet.additional = additional if additional
|
||||
packet = self.recalc_headers(packet)
|
||||
|
||||
# Set error code for NXDomain or unset it if reprocessing a response
|
||||
if packet.header.ancount < 1
|
||||
packet.header.rcode = Dnsruby::RCode::NXDOMAIN
|
||||
else
|
||||
if packet.header.qr and packet.header.get_header_rcode.to_i == 3
|
||||
packet.header.rcode = Dnsruby::RCode::NOERROR
|
||||
end
|
||||
end
|
||||
# Set response bit last to allow reprocessing of responses
|
||||
packet.header.qr = true
|
||||
# Set recursion available bit if recursion desired
|
||||
packet.header.ra = true if packet.header.rd
|
||||
return packet
|
||||
end
|
||||
|
||||
module Raw
|
||||
|
||||
#
|
||||
# Convert data to little endian unsigned short
|
||||
#
|
||||
# @param data [Fixnum, Float, Array] Input for conversion
|
||||
#
|
||||
# @return [String] Raw output
|
||||
def self.to_short_le(data)
|
||||
[data].flatten.pack('S*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data from little endian unsigned short
|
||||
#
|
||||
# @param data [String] Input for conversion
|
||||
#
|
||||
# @return [Array] Integer array output
|
||||
def self.from_short_le(data)
|
||||
data.unpack('S*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data to little endian unsigned int
|
||||
#
|
||||
# @param data [Fixnum, Float, Array] Input for conversion
|
||||
#
|
||||
# @return [String] Raw output
|
||||
def self.to_int_le(data)
|
||||
[data].flatten.pack('I*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data from little endian unsigned int
|
||||
#
|
||||
# @param data [String] Input for conversion
|
||||
#
|
||||
# @return [Array] Integer array output
|
||||
def self.from_int_le(data)
|
||||
data.unpack('I*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data to little endian unsigned long
|
||||
#
|
||||
# @param data [Fixnum, Float, Array] Input for conversion
|
||||
#
|
||||
# @return [String] Raw output
|
||||
def self.to_long_le(data)
|
||||
[data].flatten.pack('L*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data from little endian unsigned long
|
||||
#
|
||||
# @param data [String] Input for conversion
|
||||
#
|
||||
# @return [Array] Integer array output
|
||||
def self.from_long_le(data)
|
||||
data.unpack('L*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data to big endian unsigned short
|
||||
#
|
||||
# @param data [Fixnum, Float, Array] Input for conversion
|
||||
#
|
||||
# @return [String] Raw output
|
||||
def self.to_short_be(data)
|
||||
[data].flatten.pack('S>*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data from big endian unsigned short
|
||||
#
|
||||
# @param data [String] Input for conversion
|
||||
#
|
||||
# @return [Array] Integer array output
|
||||
def self.from_short_be(data)
|
||||
data.unpack('S>*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data to big endian unsigned int
|
||||
#
|
||||
# @param data [Fixnum, Float, Array] Input for conversion
|
||||
#
|
||||
# @return [String] Raw output
|
||||
def self.to_int_be(data)
|
||||
[data].flatten.pack('I>*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data from big endian unsigned int
|
||||
#
|
||||
# @param data [String] Input for conversion
|
||||
#
|
||||
# @return [Array] Integer array output
|
||||
def self.from_int_be(data)
|
||||
data.unpack('I>*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data to big endian unsigned long
|
||||
#
|
||||
# @param data [Fixnum, Float, Array] Input for conversion
|
||||
#
|
||||
# @return [String] Raw output
|
||||
def self.to_long_be(data)
|
||||
[data].flatten.pack('L>*')
|
||||
end
|
||||
|
||||
#
|
||||
# Convert data from big endian unsigned long
|
||||
#
|
||||
# @param data [String] Input for conversion
|
||||
#
|
||||
# @return [Array] Integer array output
|
||||
def self.from_long_be(data)
|
||||
data.unpack('L>*')
|
||||
end
|
||||
|
||||
#
|
||||
# Returns request ID from raw packet skipping parsing
|
||||
#
|
||||
# @param data [String] Request data
|
||||
#
|
||||
# @return [Fixnum] Request ID
|
||||
def self.request_id(data)
|
||||
self.from_short_be(data[0..1])[0]
|
||||
end
|
||||
|
||||
#
|
||||
# Returns request length from raw packet skipping parsing
|
||||
#
|
||||
# @param data [String] Request data
|
||||
#
|
||||
# @return [Fixnum] Request Length
|
||||
def self.request_length(data)
|
||||
self.from_short_le(data[0..2])[0]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,378 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'net/dns/resolver'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module DNS
|
||||
|
||||
##
|
||||
# Provides Rex::Sockets compatible version of Net::DNS::Resolver
|
||||
# Modified to work with Dnsruby::Messages, their resolvers are too heavy
|
||||
##
|
||||
class Resolver < Net::DNS::Resolver
|
||||
|
||||
Defaults = {
|
||||
:config_file => "/dev/null", # default can lead to info leaks
|
||||
:log_file => "/dev/null", # formerly $stdout, should be tied in with our loggers
|
||||
:port => 53,
|
||||
:searchlist => [],
|
||||
:nameservers => [IPAddr.new("127.0.0.1")],
|
||||
:domain => "",
|
||||
:source_port => 0,
|
||||
:source_address => IPAddr.new("0.0.0.0"),
|
||||
:retry_interval => 5,
|
||||
:retry_number => 4,
|
||||
:recursive => true,
|
||||
:defname => true,
|
||||
:dns_search => true,
|
||||
:use_tcp => false,
|
||||
:ignore_truncated => false,
|
||||
:packet_size => 512,
|
||||
:tcp_timeout => TcpTimeout.new(30),
|
||||
:udp_timeout => UdpTimeout.new(30),
|
||||
:context => {},
|
||||
:comm => nil
|
||||
}
|
||||
|
||||
attr_accessor :context, :comm
|
||||
#
|
||||
# Provide override for initializer to use local Defaults constant
|
||||
#
|
||||
# @param config [Hash] Configuration options as conusumed by parent class
|
||||
def initialize(config = {})
|
||||
raise ResolverArgumentError, "Argument has to be Hash" unless config.kind_of? Hash
|
||||
# config.key_downcase!
|
||||
@config = Defaults.merge config
|
||||
@raw = false
|
||||
|
||||
# New logger facility
|
||||
@logger = Logger.new(@config[:log_file])
|
||||
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Resolver configuration will be set in order from:
|
||||
# 1) initialize arguments
|
||||
# 2) ENV variables
|
||||
# 3) config file
|
||||
# 4) defaults (and /etc/resolv.conf for config)
|
||||
#------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Parsing config file
|
||||
#------------------------------------------------------------
|
||||
parse_config_file
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Parsing ENV variables
|
||||
#------------------------------------------------------------
|
||||
parse_environment_variables
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Parsing arguments
|
||||
#------------------------------------------------------------
|
||||
comm = config.delete(:comm)
|
||||
context = context = config.delete(:context)
|
||||
config.each do |key,val|
|
||||
next if key == :log_file or key == :config_file
|
||||
begin
|
||||
eval "self.#{key.to_s} = val"
|
||||
rescue NoMethodError
|
||||
raise ResolverArgumentError, "Option #{key} not valid"
|
||||
end
|
||||
end
|
||||
end
|
||||
#
|
||||
# Provides current proxy setting if configured
|
||||
#
|
||||
# @return [String] Current proxy configuration
|
||||
def proxies
|
||||
@config[:proxies].inspect if @config[:proxies]
|
||||
end
|
||||
|
||||
#
|
||||
# Configure proxy setting and additional timeout
|
||||
#
|
||||
# @param prox [String] SOCKS proxy connection string
|
||||
# @param timeout_added [Fixnum] Added TCP timeout to account for proxy
|
||||
def proxies=(prox, timeout_added = 250)
|
||||
return if prox.nil?
|
||||
if prox.is_a?(String) and prox.strip =~ /^socks/i
|
||||
@config[:proxies] = prox.strip
|
||||
@config[:use_tcp] = true
|
||||
self.tcp_timeout = self.tcp_timeout.to_s.to_i + timeout_added
|
||||
@logger.info "SOCKS proxy set, using TCP, increasing timeout"
|
||||
else
|
||||
raise ResolverError, "Only socks proxies supported"
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Send DNS request over appropriate transport and process response
|
||||
#
|
||||
# @param argument
|
||||
# @param type [Fixnum] Type of record to look up
|
||||
# @param cls [Fixnum] Class of question to look up
|
||||
def send(argument, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
|
||||
if @config[:nameservers].size == 0
|
||||
raise ResolverError, "No nameservers specified!"
|
||||
end
|
||||
|
||||
method = self.use_tcp? ? :send_tcp : :send_udp
|
||||
|
||||
case argument
|
||||
when Dnsruby::Message
|
||||
packet = argument
|
||||
when Net::DNS::Packet, Resolv::DNS::Message
|
||||
packet = Rex::Proto::DNS::Packet.encode_drb(argument)
|
||||
else
|
||||
packet = make_query_packet(argument,type,cls)
|
||||
end
|
||||
|
||||
# Store packet_data for performance improvements,
|
||||
# so methods don't keep on calling Packet#encode
|
||||
packet_data = packet.encode
|
||||
packet_size = packet_data.size
|
||||
|
||||
# Choose whether use TCP, UDP
|
||||
if packet_size > @config[:packet_size] # Must use TCP
|
||||
@logger.info "Sending #{packet_size} bytes using TCP due to size"
|
||||
method = :send_tcp
|
||||
else # Packet size is inside the boundaries
|
||||
if use_tcp? or !(proxies.nil? or proxies.empty?) # User requested TCP
|
||||
@logger.info "Sending #{packet_size} bytes using TCP due to tcp flag"
|
||||
method = :send_tcp
|
||||
else # Finally use UDP
|
||||
@logger.info "Sending #{packet_size} bytes using UDP"
|
||||
method = :send_udp unless method == :send_tcp
|
||||
end
|
||||
end
|
||||
|
||||
if type == Dnsruby::Types::AXFR
|
||||
@logger.warn "AXFR query, switching to TCP" unless method == :send_tcp
|
||||
method = :send_tcp
|
||||
end
|
||||
|
||||
ans = self.__send__(method,packet_data)
|
||||
|
||||
unless (ans and ans[0].length > 0)
|
||||
@logger.fatal "No response from nameservers list: aborting"
|
||||
raise NoResponseError
|
||||
return nil
|
||||
end
|
||||
|
||||
@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
|
||||
# response = Net::DNS::Packet.parse(ans[0],ans[1])
|
||||
response = Dnsruby::Message.decode(ans[0])
|
||||
|
||||
if response.header.tc and not ignore_truncated?
|
||||
@logger.warn "Packet truncated, retrying using TCP"
|
||||
self.use_tcp = true
|
||||
begin
|
||||
return send(argument,type,cls)
|
||||
ensure
|
||||
self.use_tcp = false
|
||||
end
|
||||
end
|
||||
|
||||
return response
|
||||
end
|
||||
|
||||
#
|
||||
# Send request over TCP
|
||||
#
|
||||
# @param packet_data [String] Data segment of DNS request packet
|
||||
# @param prox [String] Proxy configuration for TCP socket
|
||||
#
|
||||
# @return ans [String] Raw DNS reply
|
||||
def send_tcp(packet_data,prox = @config[:proxies])
|
||||
ans = nil
|
||||
length = [packet_data.size].pack("n")
|
||||
@config[:nameservers].each do |ns|
|
||||
begin
|
||||
socket = nil
|
||||
@config[:tcp_timeout].timeout do
|
||||
catch(:next_ns) do
|
||||
begin
|
||||
config = {
|
||||
'PeerHost' => ns.to_s,
|
||||
'PeerPort' => @config[:port].to_i,
|
||||
'Proxies' => prox,
|
||||
'Context' => @config[:context],
|
||||
'Comm' => @config[:comm]
|
||||
}
|
||||
if @config[:source_port] > 0
|
||||
config['LocalPort'] = @config[:source_port]
|
||||
end
|
||||
if @config[:source_host].to_s != '0.0.0.0'
|
||||
config['LocalHost'] = @config[:source_host] unless @config[:source_host].nil?
|
||||
end
|
||||
socket = Rex::Socket::Tcp.create(config)
|
||||
rescue
|
||||
@logger.warn "TCP Socket could not be established to #{ns}:#{@config[:port]} #{@config[:proxies]}"
|
||||
throw :next_ns
|
||||
end
|
||||
next unless socket #
|
||||
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
|
||||
socket.write(length+packet_data)
|
||||
got_something = false
|
||||
loop do
|
||||
buffer = ""
|
||||
ans = socket.recv(2)
|
||||
if ans.size == 0
|
||||
if got_something
|
||||
break #Proper exit from loop
|
||||
else
|
||||
@logger.warn "Connection reset to nameserver #{ns}, trying next."
|
||||
throw :next_ns
|
||||
end
|
||||
end
|
||||
got_something = true
|
||||
len = ans.unpack("n")[0]
|
||||
|
||||
@logger.info "Receiving #{len} bytes..."
|
||||
|
||||
if len.nil? or len == 0
|
||||
@logger.warn "Receiving 0 length packet from nameserver #{ns}, trying next."
|
||||
throw :next_ns
|
||||
end
|
||||
|
||||
while (buffer.size < len)
|
||||
left = len - buffer.size
|
||||
temp,from = socket.recvfrom(left)
|
||||
buffer += temp
|
||||
end
|
||||
|
||||
unless buffer.size == len
|
||||
@logger.warn "Malformed packet from nameserver #{ns}, trying next."
|
||||
throw :next_ns
|
||||
end
|
||||
if block_given?
|
||||
yield [buffer,["",@config[:port],ns.to_s,ns.to_s]]
|
||||
else
|
||||
return [buffer,["",@config[:port],ns.to_s,ns.to_s]]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Timeout::Error
|
||||
@logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one"
|
||||
next
|
||||
ensure
|
||||
socket.close if socket
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
#
|
||||
# Send request over UDP
|
||||
#
|
||||
# @param packet_data [String] Data segment of DNS request packet
|
||||
#
|
||||
# @return ans [String] Raw DNS reply
|
||||
def send_udp(packet_data)
|
||||
ans = nil
|
||||
response = ""
|
||||
@config[:nameservers].each do |ns|
|
||||
begin
|
||||
@config[:udp_timeout].timeout do
|
||||
begin
|
||||
config = {
|
||||
'PeerHost' => ns.to_s,
|
||||
'PeerPort' => @config[:port].to_i,
|
||||
'Context' => @config[:context],
|
||||
'Comm' => @config[:comm]
|
||||
}
|
||||
if @config[:source_port] > 0
|
||||
config['LocalPort'] = @config[:source_port]
|
||||
end
|
||||
if @config[:source_host] != IPAddr.new('0.0.0.0')
|
||||
config['LocalHost'] = @config[:source_host] unless @config[:source_host].nil?
|
||||
end
|
||||
socket = Rex::Socket::Udp.create(config)
|
||||
rescue
|
||||
@logger.warn "UDP Socket could not be established to #{ns}:#{@config[:port]}"
|
||||
return nil
|
||||
end
|
||||
@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
|
||||
#socket.sendto(packet_data, ns.to_s, @config[:port].to_i, 0)
|
||||
socket.write(packet_data)
|
||||
ans = socket.recvfrom(@config[:packet_size])
|
||||
end
|
||||
break if ans
|
||||
rescue Timeout::Error
|
||||
@logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"
|
||||
next
|
||||
end
|
||||
end
|
||||
return ans
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Perform search using the configured searchlist and resolvers
|
||||
#
|
||||
# @param name
|
||||
# @param type [Fixnum] Type of record to look up
|
||||
# @param cls [Fixnum] Class of question to look up
|
||||
#
|
||||
# @return ans [Dnsruby::Message] DNS Response
|
||||
def search(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
|
||||
|
||||
return query(name,type,cls) if name.class == IPAddr
|
||||
|
||||
# If the name contains at least one dot then try it as is first.
|
||||
if name.include? "."
|
||||
@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
|
||||
ans = query(name,type,cls)
|
||||
return ans if ans.header.ancount > 0
|
||||
end
|
||||
|
||||
# If the name doesn't end in a dot then apply the search list.
|
||||
if name !~ /\.$/ and @config[:dns_search]
|
||||
@config[:searchlist].each do |domain|
|
||||
newname = name + "." + domain
|
||||
@logger.debug "Search(#{newname},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
|
||||
ans = query(newname,type,cls)
|
||||
return ans if ans.header.ancount > 0
|
||||
end
|
||||
end
|
||||
|
||||
# Finally, if the name has no dots then try it as is.
|
||||
@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
|
||||
return query(name+".",type,cls)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# Perform query with default domain validation
|
||||
#
|
||||
# @param name
|
||||
# @param type [Fixnum] Type of record to look up
|
||||
# @param cls [Fixnum] Class of question to look up
|
||||
#
|
||||
# @return ans [Dnsruby::Message] DNS Response
|
||||
def query(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
|
||||
|
||||
return send(name,type,cls) if name.class == IPAddr
|
||||
|
||||
# If the name doesn't contain any dots then append the default domain.
|
||||
if name !~ /\./ and name !~ /:/ and @config[:defname]
|
||||
name += "." + @config[:domain]
|
||||
end
|
||||
|
||||
@logger.debug "Query(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
|
||||
|
||||
return send(name,type,cls)
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,377 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/io/gram_server'
|
||||
require 'rex/socket'
|
||||
require 'rex/proto/dns'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module DNS
|
||||
|
||||
class Server
|
||||
|
||||
class Cache
|
||||
attr_reader :records, :lock, :monitor_thread
|
||||
include Rex::Proto::DNS::Constants
|
||||
# class DNSRecordError < ::Exception
|
||||
#
|
||||
# Create DNS Server cache
|
||||
#
|
||||
def initialize
|
||||
@records = {}
|
||||
@lock = Mutex.new
|
||||
end
|
||||
|
||||
#
|
||||
# Find entries in cache, substituting names for '*' in return
|
||||
#
|
||||
# @param search [String] Name or address to search for
|
||||
# @param type [String] Record type to search for
|
||||
#
|
||||
# @return [Array] Records found
|
||||
def find(search, type = 'A')
|
||||
self.records.select do |record,expire|
|
||||
record.type == type and (expire < 1 or expire > Time.now.to_i) and
|
||||
(
|
||||
record.name == '*' or
|
||||
record.name == search or record.name[0..-2] == search or
|
||||
( record.respond_to?(:address) and record.address.to_s == search )
|
||||
)
|
||||
end.keys.map do |record|
|
||||
if search.to_s.match(MATCH_HOSTNAME) and record.name == '*'
|
||||
record = Dnsruby::RR.create(name: name, type: type, address: address)
|
||||
else
|
||||
record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Add record to cache, only when "running"
|
||||
#
|
||||
# @param record [Dnsruby::RR] Record to cache
|
||||
def cache_record(record)
|
||||
return unless @monitor_thread
|
||||
if record.is_a?(Dnsruby::RR) and
|
||||
(!record.respond_to?(:address) or Rex::Socket.is_ip_addr?(record.address.to_s)) and
|
||||
record.name.to_s.match(MATCH_HOSTNAME)
|
||||
add(record, Time.now.to_i + record.ttl)
|
||||
else
|
||||
raise "Invalid record for cache entry - #{record.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Add static record to cache
|
||||
#
|
||||
# @param name [String] Name of record
|
||||
# @param address [String] Address of record
|
||||
# @param type [String] Record type to add
|
||||
def add_static(name, address, type = 'A', replace = false)
|
||||
if Rex::Socket.is_ip_addr?(address.to_s) and
|
||||
( name.to_s.match(MATCH_HOSTNAME) or name == '*')
|
||||
find(name, type).each do |found|
|
||||
delete(found)
|
||||
end if replace
|
||||
add(Dnsruby::RR.create(name: name, type: type, address: address),0)
|
||||
else
|
||||
raise "Invalid parameters for static entry - #{name}, #{address}, #{type}"
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Prune cache entries
|
||||
#
|
||||
# @param before [Fixnum] Time in seconds before which records are evicted
|
||||
def prune(before = Time.now.to_i)
|
||||
self.records.select do |rec, expire|
|
||||
expire > 0 and expire < before
|
||||
end.each {|rec, exp| delete(rec)}
|
||||
end
|
||||
|
||||
#
|
||||
# Start the cache monitor
|
||||
#
|
||||
def start
|
||||
@monitor_thread = Rex::ThreadFactory.spawn("DNSServerCacheMonitor", false) {
|
||||
while true
|
||||
prune
|
||||
Rex::ThreadSafe.sleep(0.5)
|
||||
end
|
||||
} unless @monitor_thread
|
||||
end
|
||||
|
||||
#
|
||||
# Stop the cache monitor
|
||||
#
|
||||
# @param flush [TrueClass,FalseClass] Remove non-static entries
|
||||
def stop(flush = false)
|
||||
self.monitor_thread.kill unless @monitor_thread.nil?
|
||||
@monitor_thread = nil
|
||||
if flush
|
||||
self.records.select do |rec, expire|
|
||||
rec.ttl > 0
|
||||
end.each {|rec| delete(rec)}
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# Add a record to the cache with thread safety
|
||||
#
|
||||
# @param record [Dnsruby::RR] Record to add
|
||||
# @param expire [Fixnum] Time in seconds when record becomes stale
|
||||
def add(record, expire = 0)
|
||||
self.lock.synchronize do
|
||||
self.records[record] = expire
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Delete a record from the cache with thread safety
|
||||
#
|
||||
# @param record [Dnsruby::RR] Record to delete
|
||||
def delete(record)
|
||||
self.lock.synchronize do
|
||||
self.records.delete(record)
|
||||
end
|
||||
end
|
||||
end # Cache
|
||||
|
||||
class MockDnsClient
|
||||
attr_reader :peerhost, :peerport, :srvsock
|
||||
|
||||
#
|
||||
# Create mock DNS client
|
||||
#
|
||||
# @param host [String] PeerHost IP address
|
||||
# @param port [Fixnum] PeerPort integer
|
||||
def initialize(host, port, sock)
|
||||
@peerhost = host
|
||||
@peerport = port
|
||||
@srvsock = sock
|
||||
end
|
||||
|
||||
#
|
||||
# Test method to prevent GC/ObjectSpace abuse via class lookups
|
||||
#
|
||||
def mock_dns_client?
|
||||
true
|
||||
end
|
||||
|
||||
def write(data)
|
||||
srvsock.sendto(data, peerhost, peerport)
|
||||
end
|
||||
end
|
||||
|
||||
include Rex::IO::GramServer
|
||||
|
||||
Packet = Rex::Proto::DNS::Packet
|
||||
#
|
||||
# Create DNS Server
|
||||
#
|
||||
# @param lhost [String] Listener address
|
||||
# @param lport [Fixnum] Listener port
|
||||
# @param udp [TrueClass, FalseClass] Listen on UDP socket
|
||||
# @param tcp [TrueClass, FalseClass] Listen on TCP socket
|
||||
# @param res [Rex::Proto::DNS::Resolver] Resolver to use, nil to create a fresh one
|
||||
# @param ctx [Hash] Framework context for sockets
|
||||
# @param dblock [Proc] Handler for :dispatch_request flow control interception
|
||||
# @param sblock [Proc] Handler for :send_response flow control interception
|
||||
#
|
||||
# @return [Rex::Proto::DNS::Server] DNS Server object
|
||||
attr_accessor :serve_tcp, :serve_udp, :fwd_res, :cache
|
||||
attr_reader :serve_udp, :serve_tcp, :sock_options, :lock, :udp_sock, :tcp_sock
|
||||
def initialize(lhost = '0.0.0.0', lport = 53, udp = true, tcp = false, res = nil, comm = nil, ctx = {}, dblock = nil, sblock = nil)
|
||||
|
||||
@serve_udp = udp
|
||||
@serve_tcp = tcp
|
||||
@sock_options = {
|
||||
'LocalHost' => lhost,
|
||||
'LocalPort' => lport,
|
||||
'Context' => ctx,
|
||||
'Comm' => comm
|
||||
}
|
||||
self.fwd_res = res.nil? ? Rex::Proto::DNS::Resolver.new(:comm => comm, :context => ctx) : res
|
||||
self.listener_thread = nil
|
||||
self.dispatch_request_proc = dblock
|
||||
self.send_response_proc = sblock
|
||||
self.cache = Cache.new
|
||||
@lock = Mutex.new
|
||||
end
|
||||
|
||||
#
|
||||
# Switch DNS forwarders in resolver with thread safety
|
||||
#
|
||||
# @param ns [Array, String] List of (or single) nameservers to use
|
||||
def switchns(ns = [])
|
||||
if ns.respond_to?(:split)
|
||||
ns = [ns]
|
||||
end
|
||||
self.lock.synchronize do
|
||||
self.fwd_res.nameserver = ns
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Check if server is running
|
||||
#
|
||||
def running?
|
||||
self.listener_thread and self.listener_thread.alive?
|
||||
end
|
||||
|
||||
#
|
||||
# Start the DNS server and cache
|
||||
# @param start_cache [TrueClass, FalseClass] stop the cache
|
||||
def start(start_cache = true)
|
||||
|
||||
if self.serve_udp
|
||||
@udp_sock = Rex::Socket::Udp.create(self.sock_options)
|
||||
self.listener_thread = Rex::ThreadFactory.spawn("UDPDNSServerListener", false) {
|
||||
monitor_listener
|
||||
}
|
||||
end
|
||||
|
||||
if self.serve_tcp
|
||||
@tcp_sock = Rex::Socket::TcpServer.create(self.sock_options)
|
||||
self.tcp_sock.on_client_data_proc = Proc.new { |cli|
|
||||
on_client_data(cli)
|
||||
}
|
||||
self.tcp_sock.start
|
||||
if !self.serve_udp
|
||||
self.listener_thread = tcp_sock.listener_thread
|
||||
end
|
||||
end
|
||||
|
||||
self.cache.start if start_cache
|
||||
end
|
||||
|
||||
#
|
||||
# Stop the DNS server and cache
|
||||
#
|
||||
# @param flush_cache [TrueClass,FalseClass] Flush eDNS cache on stop
|
||||
def stop(flush_cache = false)
|
||||
ensure_close = [self.udp_sock, self.tcp_sock].compact
|
||||
begin
|
||||
self.listener_thread.kill if self.listener_thread.respond_to?(:kill)
|
||||
self.listener_thread = nil
|
||||
ensure
|
||||
while csock = ensure_close.shift
|
||||
csock.stop if csock.respond_to?(:stop)
|
||||
csock.close unless csock.respond_to?(:close) and csock.closed?
|
||||
end
|
||||
end
|
||||
self.cache.stop(flush_cache)
|
||||
end
|
||||
|
||||
#
|
||||
# Process client request, handled with dispatch_request_proc if set
|
||||
#
|
||||
# @param cli [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request
|
||||
# @param data [String] raw DNS request data
|
||||
def dispatch_request(cli, data)
|
||||
if self.dispatch_request_proc
|
||||
self.dispatch_request_proc.call(cli,data)
|
||||
else
|
||||
default_dispatch_request(cli,data)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Default DNS request dispatcher, attempts to find
|
||||
# response records in cache or forwards request upstream
|
||||
#
|
||||
# @param cli [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request
|
||||
# @param data [String] raw DNS request data
|
||||
def default_dispatch_request(cli,data)
|
||||
return if data.strip.empty?
|
||||
req = Packet.encode_drb(data)
|
||||
forward = req.dup
|
||||
# Find cached items, remove request from forwarded packet
|
||||
req.question.each do |ques|
|
||||
cached = self.cache.find(ques.qname, ques.qtype.to_s)
|
||||
if cached.empty?
|
||||
next
|
||||
else
|
||||
req.answer = req.answer + cached
|
||||
forward.question.delete(ques)
|
||||
end
|
||||
end
|
||||
# Forward remaining requests, cache responses
|
||||
if forward.question.count > 0 and @fwd_res
|
||||
forwarded = self.fwd_res.send(validate_packet(forward))
|
||||
req.answer = req.answer + forwarded.answer
|
||||
forwarded.answer.each do |ans|
|
||||
self.cache.cache_record(ans)
|
||||
end
|
||||
req.header.ra = true # Set recursion bit
|
||||
end
|
||||
# Finalize answers in response
|
||||
# Check for empty response prior to sending
|
||||
if req.answer.size < 1
|
||||
req.header.rCode = Dnsruby::RCode::NOERROR
|
||||
end
|
||||
req.header.qr = true # Set response bit
|
||||
send_response(cli, validate_packet(req).data)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the hardcore alias for the DNS service
|
||||
#
|
||||
def self.hardcore_alias(*args)
|
||||
"#{(args[0] || '')}#{(args[1] || '')}"
|
||||
end
|
||||
|
||||
#
|
||||
# DNS server.
|
||||
#
|
||||
def alias
|
||||
"DNS Server"
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
#
|
||||
# This method monitors the listener socket for new connections and calls
|
||||
# the +on_client_connect+ callback routine.
|
||||
#
|
||||
def monitor_listener
|
||||
while true
|
||||
rds = [self.udp_sock]
|
||||
wds = []
|
||||
eds = [self.udp_sock]
|
||||
|
||||
r,_,_ = ::IO.select(rds,wds,eds,1)
|
||||
|
||||
if (r != nil and r[0] == self.udp_sock)
|
||||
buf,host,port = self.udp_sock.recvfrom(65535)
|
||||
# Mock up a client object for sending back data
|
||||
cli = MockDnsClient.new(host, port, r[0])
|
||||
dispatch_request(cli, buf)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Processes request coming from client
|
||||
#
|
||||
# @param cli [Rex::Socket::Tcp] Client sending request
|
||||
def on_client_data(cli)
|
||||
begin
|
||||
data = cli.read(65535)
|
||||
|
||||
raise ::EOFError if not data
|
||||
raise ::EOFError if data.empty?
|
||||
dispatch_request(cli, data)
|
||||
rescue EOFError => e
|
||||
self.tcp_socket.close_client(cli) if cli
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,107 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/exploit/dns'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::DNS::Client
|
||||
include Msf::Exploit::Remote::DNS::Server
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Native DNS Server (Example)',
|
||||
'Description' => %q{
|
||||
This module provides a Rex based DNS service which can store static entries,
|
||||
resolve names over pivots, and serve DNS requests across routed session comms.
|
||||
DNS tunnels can operate across the the Rex switchboard, and DNS other modules
|
||||
can use this as a template. Setting static records via hostfile allows for DNS
|
||||
spoofing attacks without direct traffic manipulation at the handlers. handlers
|
||||
for requests and responses provided here mimic the internal Rex functionality,
|
||||
but utilize methods within this module's namespace to output content processed
|
||||
in the Proc contexts via vprint_status.
|
||||
},
|
||||
'Author' => 'RageLtMan <rageltman[at]sempervictus>',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => []
|
||||
))
|
||||
end
|
||||
|
||||
#
|
||||
# Wrapper for service execution and cleanup
|
||||
#
|
||||
def run
|
||||
begin
|
||||
start_service
|
||||
service.wait
|
||||
rescue Rex::BindFailed => e
|
||||
print_error "Failed to bind to port #{datastore['RPORT']}: #{e.message}"
|
||||
ensure
|
||||
stop_service(true)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Creates Proc to handle incoming requests
|
||||
#
|
||||
def on_dispatch_request(cli,data)
|
||||
return if data.strip.empty?
|
||||
req = Packet.encode_drb(data)
|
||||
peer = "#{cli.peerhost}:#{cli.peerport}"
|
||||
asked = req.question.map(&:qname).map(&:to_s).join(', ')
|
||||
vprint_status("Received request for #{asked} from #{peer}")
|
||||
answered = []
|
||||
# Find cached items, remove request from forwarded packet
|
||||
req.question.each do |ques|
|
||||
cached = service.cache.find(ques.qname, ques.qtype.to_s)
|
||||
if cached.empty?
|
||||
next
|
||||
else
|
||||
req.instance_variable_set(:@answer, (req.answer + cached).uniq)
|
||||
answered << ques
|
||||
cached.map do |hit|
|
||||
if hit.respond_to?(:address)
|
||||
hit.name.to_s + ':' + hit.address.to_s + ' ' + hit.type.to_s
|
||||
else
|
||||
hit.name.to_s + ' ' + hit.type.to_s
|
||||
end
|
||||
end.each {|h| vprint_status("Cache hit for #{h}")}
|
||||
end
|
||||
end unless service.cache.nil?
|
||||
# Forward remaining requests, cache responses
|
||||
if answered.count < req.question.count and service.fwd_res
|
||||
if !req.header.rd
|
||||
vprint_status("Recursion forbidden in query for #{req.question.first.name} from #{peer}")
|
||||
else
|
||||
forward = req.dup
|
||||
# forward.question = req.question - answered
|
||||
forward.instance_variable_set(:@question, req.question - answered)
|
||||
forwarded = service.fwd_res.send(Packet.validate(forward))
|
||||
forwarded.answer.each do |ans|
|
||||
rstring = ans.respond_to?(:address) ? "#{ans.name}:#{ans.address}" : ans.name
|
||||
vprint_status("Caching response #{rstring} #{ans.type}")
|
||||
service.cache.cache_record(ans)
|
||||
end unless service.cache.nil?
|
||||
# Merge the answers and use the upstream response
|
||||
forward.instance_variable_set(:@question, (req.answer + forwarded.answer).uniq)
|
||||
req = forwarded
|
||||
end
|
||||
end
|
||||
service.send_response(cli, Packet.validate(Packet.generate_response(req)).encode)
|
||||
end
|
||||
|
||||
#
|
||||
# Creates Proc to handle outbound responses
|
||||
#
|
||||
def on_send_response(cli,data)
|
||||
res = Packet.encode_drb(data)
|
||||
peer = "#{cli.peerhost}:#{cli.peerport}"
|
||||
asked = res.question.map(&:qname).map(&:to_s).join(', ')
|
||||
vprint_status("Sending response for #{asked} to #{peer}")
|
||||
cli.write(data)
|
||||
end
|
||||
|
||||
|
||||
end
|
|
@ -0,0 +1,162 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/exploit/dns'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Capture
|
||||
include Msf::Exploit::Remote::DNS::Client
|
||||
include Msf::Exploit::Remote::DNS::Server
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Native DNS Spoofer (Example)',
|
||||
'Description' => %q{
|
||||
This module provides a Rex based DNS service to resolve queries intercepted
|
||||
via the capture mixin. Configure STATIC_ENTRIES to contain host-name mappings
|
||||
desired for spoofing using a hostsfile or space/semicolon separated entries.
|
||||
In default configuration, the service operates as a normal native DNS server
|
||||
with the exception of consuming from and writing to the wire as opposed to a
|
||||
listening socket. Best when compromising routers or spoofing L2 in order to
|
||||
prevent return of the real reply which causes a race condition. The method
|
||||
by which replies are filtered is up to the user (though iptables works fine).
|
||||
},
|
||||
'Author' => 'RageLtMan <rageltman[at]sempervictus>',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => []
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('FILTER', [false, 'The filter string for capturing traffic', 'dst port 53']),
|
||||
OptAddress.new('SRVHOST', [true, 'The local host to listen on for DNS services.', '127.0.2.2'])
|
||||
])
|
||||
|
||||
deregister_options('PCAPFILE')
|
||||
end
|
||||
|
||||
#
|
||||
# Wrapper for service execution and cleanup
|
||||
#
|
||||
def run
|
||||
begin
|
||||
start_service
|
||||
capture_traffic
|
||||
service.wait
|
||||
rescue Rex::BindFailed => e
|
||||
print_error "Failed to bind to port #{datastore['RPORT']}: #{e.message}"
|
||||
ensure
|
||||
@capture_thread.kill if @capture_thread
|
||||
close_pcap
|
||||
stop_service(true)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Generates reply with src and dst reversed
|
||||
# Maintains original packet structure, proto, etc, changes ip_id
|
||||
#
|
||||
def reply_packet(pack)
|
||||
rep = pack.dup
|
||||
rep.eth_dst, rep.eth_src = rep.eth_src, rep.eth_dst
|
||||
rep.ip_dst, rep.ip_src = rep.ip_src, rep.ip_dst
|
||||
if pack.is_udp?
|
||||
rep.udp_dst, rep.udp_src = rep.udp_src, rep.udp_dst
|
||||
else
|
||||
rep.tcp_dst, rep.tcp_src = rep.tcp_src, rep.tcp_dst
|
||||
end
|
||||
rep.ip_id = StructFu::Int16.new(rand(2**16))
|
||||
return rep
|
||||
end
|
||||
|
||||
#
|
||||
# Configures capture and handoff
|
||||
#
|
||||
def capture_traffic
|
||||
check_pcaprub_loaded()
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
open_pcap({'FILTER' => datastore['FILTER']})
|
||||
@capture_thread = Rex::ThreadFactory.spawn("DNSSpoofer", false) do
|
||||
each_packet do |pack|
|
||||
begin
|
||||
parsed = PacketFu::Packet.parse(pack)
|
||||
reply = reply_packet(parsed)
|
||||
service.dispatch_request(reply, parsed.payload)
|
||||
rescue => e
|
||||
vprint_status("PacketFu could not parse captured packet")
|
||||
dlog(e.backtrace)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Creates Proc to handle incoming requests
|
||||
#
|
||||
def on_dispatch_request(cli,data)
|
||||
peer = "#{cli.ip_daddr}:" << (cli.is_udp? ? "#{cli.udp_dst}" : "#{cli.tcp_dst}")
|
||||
# Deal with non DNS traffic
|
||||
begin
|
||||
req = Packet.encode_net(data)
|
||||
rescue => e
|
||||
print_error("Could not decode payload segment of packet from #{peer}, check log")
|
||||
dlog e.backtrace
|
||||
return
|
||||
end
|
||||
answered = []
|
||||
# Find cached items, remove request from forwarded packet
|
||||
req.question.each do |ques|
|
||||
cached = service.cache.find(ques.qName, ques.qType.to_s)
|
||||
if cached.empty?
|
||||
next
|
||||
else
|
||||
req.answer = (req.answer + cached).uniq
|
||||
answered << ques
|
||||
end
|
||||
end
|
||||
if answered.count < req.question.count and service.fwd_res
|
||||
if !req.header.recursive?
|
||||
vprint_status("Recursion forbidden in query for #{req.question.first.name} from #{peer}")
|
||||
else
|
||||
forward = req.dup
|
||||
forward.question = req.question - answered
|
||||
forwarded = service.fwd_res.send(Packet.validate(forward))
|
||||
forwarded.answer.each do |ans|
|
||||
rstring = ans.respond_to?(:address) ? "#{ans.name}:#{ans.address}" : ans.name
|
||||
vprint_status("Caching response #{rstring} #{ans.type}")
|
||||
service.cache.cache_record(ans)
|
||||
end unless service.cache.nil?
|
||||
# Merge the answers and use the upstream response
|
||||
forwarded.answer = (req.answer + forwarded.answer).uniq
|
||||
req = forwarded
|
||||
end
|
||||
end
|
||||
service.send_response(cli, Packet.validate(Packet.generate_response(req)).data)
|
||||
end
|
||||
|
||||
#
|
||||
# Creates Proc to handle outbound responses
|
||||
#
|
||||
def on_send_response(cli,data)
|
||||
cli.payload = data
|
||||
cli.recalc
|
||||
inject cli.to_s
|
||||
sent_info(cli,data) if datastore['VERBOSE']
|
||||
end
|
||||
|
||||
#
|
||||
# Prints information about spoofed packet after injection to reduce latency of operation
|
||||
# Shown to improve response time by >50% from ~1ms -> 0.3-0.4ms
|
||||
#
|
||||
def sent_info(cli,data)
|
||||
net = Packet.encode_net(data)
|
||||
peer = "#{cli.ip_daddr}:" << (cli.is_udp? ? "#{cli.udp_dst}" : "#{cli.tcp_dst}")
|
||||
asked = net.question.map(&:qName).join(', ')
|
||||
vprint_good("Sent packet with header:\n#{cli.inspect}")
|
||||
vprint_good("Spoofed records for #{asked} to #{peer}")
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue