c65c03722c
Dnsruby provides advanced options like DNSSEC in its data format and is a current and well supported library. The infrastructure services - resolver, server, etc, were designed for a standalone configuration, and carry entirely too much weight and redundancy to implement for this context. Instead of porting over their native resolver, update the Net::DNS subclassed Rex Resolver to use Dnsruby data formats and method calls. Update the Msf namespace infrastructure mixins and native server module with new method calls and workarounds for some instance variables having only readers without writers. Implement the Rex ServerManager to start and stop the DNS service adding relevant alias methods to the Rex::Proto::DNS::Server class. Rex services are designed to be modular and lightweight, as well as implement the sockets, threads, and other low-level interfaces. Dnsruby's operations classes implement their own threading and socket semantics, and do not fit with the modular mixin workflow used throughout Framework. So while the updated resolver can be seen as adding rubber to the tire fire, converting to dnsruby's native classes for resolvers, servers, and caches, would be more like adding oxy acetylene and heavy metals. Testing: Internal tests for resolution of different record types locally and over pivot sessions.
316 lines
8.1 KiB
Ruby
316 lines
8.1 KiB
Ruby
# -*- 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
|