Skip to content

Commit c65c037

Browse files
author
RageLtMan
committed
Migrate native DNS services to Dnsruby data format
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.
1 parent f76adf6 commit c65c037

File tree

6 files changed

+176
-76
lines changed

6 files changed

+176
-76
lines changed

lib/msf/core/exploit/dns/client.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ def initialize(info = {})
4747
], Exploit::Remote::DNS::Client
4848
)
4949

50-
register_autofilter_ports([ 53 ])
51-
register_autofilter_services(%W{ dns })
50+
register_autofilter_ports([ 53 ]) if respond_to?(:register_autofilter_ports)
51+
register_autofilter_services(%W{ dns }) if respond_to?(:register_autofilter_services)
5252
end
5353

5454

@@ -58,7 +58,7 @@ def initialize(info = {})
5858
# @param domain [String] Domain for which to request a record
5959
# @param type [String] Type of record to request for domain
6060
#
61-
# @return [Net::DNS::RR] DNS response
61+
# @return [Dnsruby::RR] DNS response
6262
def query(domain = datastore['DOMAIN'], type = 'A')
6363
client.query(domain, type)
6464
end
@@ -110,7 +110,7 @@ def switchdns(domain)
110110
if datastore['NS'].blank?
111111
resp_soa = client.query(target, "SOA")
112112
if (resp_soa)
113-
(resp_soa.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr|
113+
(resp_soa.answer.select { |i| i.is_a?(Dnsruby::RR::SOA)}).each do |rr|
114114
resp_1_soa = client.search(rr.mname)
115115
if (resp_1_soa and resp_1_soa.answer[0])
116116
set_nameserver(resp_1_soa.answer.map(&:address).compact.map(&:to_s))
@@ -139,7 +139,7 @@ def wildcard(domain, type = "A")
139139
if response.answer.length != 0
140140
vprint_status("This domain has wildcards enabled!!")
141141
response.answer.each do |rr|
142-
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME
142+
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Dnsruby::RR::CNAME
143143
addr = rr.address.to_s
144144
end
145145
end

lib/msf/core/exploit/dns/server.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: binary -*-
22
require 'msf/core'
33
require 'rex/proto/dns'
4-
4+
require 'msf/core/exploit/dns/common'
55

66
module Msf
77

@@ -12,7 +12,7 @@ module Msf
1212
###
1313
module Exploit::Remote::DNS
1414
module Server
15-
include Common
15+
include Exploit::Remote::DNS::Common
1616
include Exploit::Remote::SocketServer
1717

1818
#
@@ -110,7 +110,8 @@ def start_service
110110
begin
111111

112112
comm = _determine_server_comm
113-
self.service = Rex::Proto::DNS::Server.new(
113+
self.service = Rex::ServiceManager.start(
114+
Rex::Proto::DNS::Server,
114115
datastore['SRVHOST'],
115116
datastore['SRVPORT'],
116117
datastore['DnsServerUdp'],
@@ -154,7 +155,7 @@ def start_service
154155
# Stops the server
155156
# @param destroy [TrueClass,FalseClass] Dereference the server object
156157
def stop_service(destroy = false)
157-
self.service.stop unless self.service.nil?
158+
Rex::ServiceManager.stop_service(self.service) if self.service
158159
if destroy
159160
@dns_resolver = nil
160161
self.service = nil

lib/rex/proto/dns/packet.rb

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
# -*- coding: binary -*-
2+
13
require 'net/dns'
24
require 'resolv'
5+
require 'dnsruby'
36

47
module Rex
58
module Proto
@@ -21,26 +24,26 @@ def self.valid_hostname?(subject = '')
2124
# Reconstructs a packet with both standard DNS libraries
2225
# Ensures that headers match the payload
2326
#
24-
# @param packet [String, Net::DNS::Packet] Data to be validated
27+
# @param packet [String, Net::DNS::Packet, Dnsruby::Message] Data to be validated
2528
#
26-
# @return [Net::DNS::Packet]
29+
# @return [Dnsruby::Message]
2730
def self.validate(packet)
28-
self.encode_net(self.encode_res(self.encode_raw(packet)))
31+
self.encode_drb(self.encode_net(self.encode_res(packet)))
2932
end
3033

3134
#
3235
# Sets header values to match packet content
3336
#
34-
# @param packet [String] Net::DNS::Packet, Resolv::DNS::Message]
37+
# @param packet [String] Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message]
3538
#
36-
# @return [Net::DNS::Packet]
39+
# @return [Dnsruby::Message]
3740
def self.recalc_headers(packet)
38-
packet = self.encode_net(packet)
41+
packet = self.encode_drb(packet)
3942
{
40-
:qdCount= => :question,
41-
:anCount= => :answer,
42-
:nsCount= => :authority,
43-
:arCount= => :additional
43+
:qdcount= => :question,
44+
:ancount= => :answer,
45+
:nscount= => :authority,
46+
:arcount= => :additional
4447
}.each do |header,body|
4548
packet.header.send(header,packet.send(body).count)
4649
end
@@ -51,36 +54,48 @@ def self.recalc_headers(packet)
5154
#
5255
# Reads a packet into the Net::DNS::Packet format
5356
#
54-
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message] Input data
57+
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
5558
#
5659
# @return [Net::DNS::Packet]
5760
def self.encode_net(packet)
58-
return packet if packet.respond_to?(:data)
61+
return packet if packet.is_a?(Net::DNS::Packet)
5962
Net::DNS::Packet.parse(
60-
packet.respond_to?(:encode) ? packet.encode : packet
63+
self.encode_raw(packet)
6164
)
6265
end
6366

6467
# Reads a packet into the Resolv::DNS::Message format
6568
#
66-
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message] Input data
69+
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
6770
#
6871
# @return [Resolv::DNS::Message]
6972
def self.encode_res(packet)
70-
return packet if packet.respond_to?(:encode)
73+
return packet if packet.is_a?(Resolv::DNS::Message)
7174
Resolv::DNS::Message.decode(
72-
packet.respond_to?(:data) ? packet.data : packet
75+
self.encode_raw(packet)
76+
)
77+
end
78+
79+
# Reads a packet into the Dnsruby::Message format
80+
#
81+
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
82+
#
83+
# @return [Dnsruby::Message]
84+
def self.encode_drb(packet)
85+
return packet if packet.is_a?(Dnsruby::Message)
86+
Dnsruby::Message.decode(
87+
self.encode_raw(packet)
7388
)
7489
end
7590

7691
# Reads a packet into the raw String format
7792
#
78-
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message] Input data
93+
# @param data [String, Net::DNS::Packet, Resolv::DNS::Message, Dnsruby::Message] Input data
7994
#
80-
# @return [Resolv::DNS::Message]
95+
# @return [String]
8196
def self.encode_raw(packet)
8297
return packet unless packet.respond_to?(:encode) or packet.respond_to?(:data)
83-
packet.respond_to?(:data) ? packet.data : packet.encode
98+
(packet.respond_to?(:data) ? packet.data : packet.encode).force_encoding('binary')
8499
end
85100

86101
#
@@ -91,16 +106,16 @@ def self.encode_raw(packet)
91106
# @param cls [Fixnum] Class of dns record to query
92107
# @param recurse [Fixnum] Recursive query or not
93108
#
94-
# @return [Net::DNS::Packet] request packet
95-
def self.generate_request(subject, type = Net::DNS::A, cls = Net::DNS::IN, recurse = 1)
109+
# @return [Dnsruby::Message] request packet
110+
def self.generate_request(subject, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN, recurse = 1)
96111
case subject
97112
when IPAddr
98113
name = subject.reverse
99-
type = Net::DNS::PTR
114+
type = Dnsruby::Types::PTR
100115
when /\d/ # Contains a number, try to see if it's an IP or IPv6 address
101116
begin
102117
name = IPAddr.new(subject).reverse
103-
type = Net::DNS::PTR
118+
type = Dnsruby::Types::PTR
104119
rescue ArgumentError
105120
name = subject if self.valid_hostname?(subject)
106121
end
@@ -109,9 +124,9 @@ def self.generate_request(subject, type = Net::DNS::A, cls = Net::DNS::IN, recur
109124
end
110125

111126
# Create the packet
112-
packet = Net::DNS::Packet.new(name,type,cls)
127+
packet = Dnsruby::Message.new(name, type, cls)
113128

114-
if packet.query?
129+
if packet.header.opcode == Dnsruby::OpCode::Query
115130
packet.header.recursive = recurse
116131
end
117132

@@ -128,26 +143,26 @@ def self.generate_request(subject, type = Net::DNS::A, cls = Net::DNS::IN, recur
128143
# @param authority [Array] Set of authority records to provide in the response
129144
# @param additional [Array] Set of additional records to provide in the response
130145
#
131-
# @return [Net::DNS::Packet] Response packet
146+
# @return [Dnsruby::Message] Response packet
132147
def self.generate_response(request, answer = nil, authority = nil, additional = nil)
133-
packet = self.encode_net(request)
148+
packet = self.encode_drb(request)
134149
packet.answer = answer if answer
135150
packet.authority = authority if authority
136151
packet.additional = additional if additional
137152
packet = self.recalc_headers(packet)
138153

139154
# Set error code for NXDomain or unset it if reprocessing a response
140-
if packet.header.anCount < 1
141-
packet.header.rCode = 3
155+
if packet.header.ancount < 1
156+
packet.header.rcode = Dnsruby::RCode::NXDOMAIN
142157
else
143-
if packet.header.response? and packet.header.rCode.code == 3
144-
packet.header.rCode = 0
158+
if packet.header.qr and packet.header.get_header_rcode.to_i == 3
159+
packet.header.rcode = Dnsruby::RCode::NOERROR
145160
end
146161
end
147162
# Set response bit last to allow reprocessing of responses
148-
packet.header.qr = 1
163+
packet.header.qr = true
149164
# Set recursion available bit if recursion desired
150-
packet.header.ra = 1 if packet.header.recursive?
165+
packet.header.ra = true if packet.header.rd
151166
return packet
152167
end
153168

lib/rex/proto/dns/resolver.rb

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# -*- coding: binary -*-
2+
13
require 'net/dns/resolver'
24

35
module Rex
@@ -6,6 +8,7 @@ module DNS
68

79
##
810
# Provides Rex::Sockets compatible version of Net::DNS::Resolver
11+
# Modified to work with Dnsruby::Messages, their resolvers are too heavy
912
##
1013
class Resolver < Net::DNS::Resolver
1114

@@ -112,24 +115,25 @@ def proxies=(prox, timeout_added = 250)
112115
# @param argument
113116
# @param type [Fixnum] Type of record to look up
114117
# @param cls [Fixnum] Class of question to look up
115-
def send(argument,type=Net::DNS::A,cls=Net::DNS::IN)
118+
def send(argument, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
116119
if @config[:nameservers].size == 0
117120
raise ResolverError, "No nameservers specified!"
118121
end
119122

120123
method = self.use_tcp? ? :send_tcp : :send_udp
121124

122-
if argument.kind_of? Net::DNS::Packet
125+
case argument
126+
when Dnsruby::Message
123127
packet = argument
124-
elsif argument.kind_of? Resolv::DNS::Message
125-
packet = Net::DNS::Packet.parse(argument.encode)
128+
when Net::DNS::Packet, Resolv::DNS::Message
129+
packet = Rex::Proto::DNS::Packet.encode_drb(argument)
126130
else
127131
packet = make_query_packet(argument,type,cls)
128132
end
129133

130134
# Store packet_data for performance improvements,
131-
# so methods don't keep on calling Packet#data
132-
packet_data = packet.data
135+
# so methods don't keep on calling Packet#encode
136+
packet_data = packet.encode
133137
packet_size = packet_data.size
134138

135139
# Choose whether use TCP, UDP
@@ -146,7 +150,7 @@ def send(argument,type=Net::DNS::A,cls=Net::DNS::IN)
146150
end
147151
end
148152

149-
if type == Net::DNS::AXFR
153+
if type == Dnsruby::Types::AXFR
150154
@logger.warn "AXFR query, switching to TCP" unless method == :send_tcp
151155
method = :send_tcp
152156
end
@@ -160,9 +164,10 @@ def send(argument,type=Net::DNS::A,cls=Net::DNS::IN)
160164
end
161165

162166
@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
163-
response = Net::DNS::Packet.parse(ans[0],ans[1])
167+
# response = Net::DNS::Packet.parse(ans[0],ans[1])
168+
response = Dnsruby::Message.decode(ans[0])
164169

165-
if response.header.truncated? and not ignore_truncated?
170+
if response.header.tc and not ignore_truncated?
166171
@logger.warn "Packet truncated, retrying using TCP"
167172
self.use_tcp = true
168173
begin
@@ -215,7 +220,7 @@ def send_tcp(packet_data,prox = @config[:proxies])
215220
got_something = false
216221
loop do
217222
buffer = ""
218-
ans = socket.recv(Net::DNS::INT16SZ)
223+
ans = socket.recv(2)
219224
if ans.size == 0
220225
if got_something
221226
break #Proper exit from loop
@@ -305,7 +310,68 @@ def send_udp(packet_data)
305310
end
306311
return ans
307312
end
308-
end
313+
314+
315+
#
316+
# Perform search using the configured searchlist and resolvers
317+
#
318+
# @param name
319+
# @param type [Fixnum] Type of record to look up
320+
# @param cls [Fixnum] Class of question to look up
321+
#
322+
# @return ans [Dnsruby::Message] DNS Response
323+
def search(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
324+
325+
return query(name,type,cls) if name.class == IPAddr
326+
327+
# If the name contains at least one dot then try it as is first.
328+
if name.include? "."
329+
@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
330+
ans = query(name,type,cls)
331+
return ans if ans.header.ancount > 0
332+
end
333+
334+
# If the name doesn't end in a dot then apply the search list.
335+
if name !~ /\.$/ and @config[:dns_search]
336+
@config[:searchlist].each do |domain|
337+
newname = name + "." + domain
338+
@logger.debug "Search(#{newname},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
339+
ans = query(newname,type,cls)
340+
return ans if ans.header.ancount > 0
341+
end
342+
end
343+
344+
# Finally, if the name has no dots then try it as is.
345+
@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
346+
return query(name+".",type,cls)
347+
348+
end
349+
350+
end
351+
352+
#
353+
# Perform query with default domain validation
354+
#
355+
# @param name
356+
# @param type [Fixnum] Type of record to look up
357+
# @param cls [Fixnum] Class of question to look up
358+
#
359+
# @return ans [Dnsruby::Message] DNS Response
360+
def query(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)
361+
362+
return send(name,type,cls) if name.class == IPAddr
363+
364+
# If the name doesn't contain any dots then append the default domain.
365+
if name !~ /\./ and name !~ /:/ and @config[:defname]
366+
name += "." + @config[:domain]
367+
end
368+
369+
@logger.debug "Query(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"
370+
371+
return send(name,type,cls)
372+
373+
end
374+
309375

310376
end
311377
end

0 commit comments

Comments
 (0)