nesser 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bffba7aa303feb0c9264e87bea0b4071dd43de0a
4
+ data.tar.gz: f9d20c7da26f034657c8fbb16ded9222571d1b1b
5
+ SHA512:
6
+ metadata.gz: 5a92a2bd9131b145d848cfc2204dbcbc5468424e43fb60cf8a8cd08d1e5480cfae325f5ec47c0e5824c20c7fcb99285a923bfa6a747e6c2bbdfaf616bb631232
7
+ data.tar.gz: 42c424af1ba877d784b05f1a40cb1577067d47ff6b06b672f62bb5f7a5393ace29abbcd8a1e305d282e098fe2751862dc4a1063637884b8c7ede1717370ec79c
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.1
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nesser.gemspec
4
+ gemspec
@@ -0,0 +1,28 @@
1
+ # Nesser
2
+
3
+ A DNS client and server class, written for Dnscat2 (and other similar projects).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'nesser'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install nesser
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ Bug reports and pull requests are welcome on GitHub at https://github.com/iagox86/nesser
28
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nesser"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,149 @@
1
+ # Encoding: ASCII-8BIT
2
+ ##
3
+ # nesser.rb
4
+ # Created Oct 7, 2015
5
+ # By Ron Bowes
6
+ #
7
+ # See: LICENSE.md
8
+ #
9
+ # I had nothing but trouble using rubydns (which became celluloid-dns, whose
10
+ # documentation is just flat out wrong), and I only really need a very small
11
+ # subset of DNS functionality, so I decided that I should just wrote my own.
12
+ #
13
+ # Note: after writing this, I noticed that Resolv::DNS exists in the Ruby
14
+ # language, I need to check if I can use that.
15
+ #
16
+ # There are two methods for using this library: as a client (to make a query)
17
+ # or as a server (to listen for queries and respond to them).
18
+ #
19
+ # To make a query, use Nesser.query:
20
+ #
21
+ # Nesser.query("google.com") do |response|
22
+ # ...
23
+ # end
24
+ #
25
+ # `response` will be of type Nesser::Packet.
26
+ #
27
+ # To listen for queries, create a new instance of Nesser, which will begin
28
+ # listening on a port, but won't actually handle queries yet:
29
+ #
30
+ # nesser = Nesser.new("0.0.0.0", 53) do |transaction|
31
+ # ...
32
+ # end
33
+ #
34
+ # `transaction` is of type Nesser::Transaction, and allows you to respond to the
35
+ # request either immediately or asynchronously.
36
+ #
37
+ # Nesser currently supports the following record types: A, NS, CNAME, SOA, MX,
38
+ # TXT, and AAAA.
39
+ ##
40
+
41
+ require 'ipaddr'
42
+ require 'socket'
43
+ require 'timeout'
44
+
45
+ require "nesser/version"
46
+
47
+ module Nesser
48
+ class Nesser
49
+ attr_reader :thread
50
+
51
+ def initialize(s:, logger:, host:"0.0.0.0", port:53)
52
+ @s = s
53
+ @s.bind(host, port)
54
+ @logger = logger
55
+
56
+ @thread = Thread.new() do
57
+ begin
58
+ loop do
59
+ # Grab all the data we can off the socket
60
+ data = @s.recvfrom(65536)
61
+
62
+ begin
63
+ # Data is an array where the first element is the actual data, and the second is the host/port
64
+ request = Packet.parse(data[0])
65
+ rescue DnsException => e
66
+ logger.error("Failed to parse the DNS packet: %s" % e.to_s())
67
+ next
68
+ end
69
+
70
+ # Create a transaction object, which we can use to respond
71
+ transaction = Transaction.new(
72
+ s: @s,
73
+ request_packet: request,
74
+ host: data[1][3],
75
+ port: data[1][1],
76
+ )
77
+
78
+ begin
79
+ proc.call(transaction)
80
+ rescue StandardError => e
81
+ logger.error("Error thrown while processing the DNS packet: %s" % e.to_s())
82
+ logger.info(e.backtrace())
83
+ transaction.error!(RCODE_SERVER_FAILURE)
84
+ end
85
+ end
86
+ ensure
87
+ @s.close()
88
+ end
89
+ end
90
+ end
91
+
92
+ # Kill the listener
93
+ def stop()
94
+ if(@thread.nil?)
95
+ @logger.error("Tried to stop a listener that wasn't listening!")
96
+ return
97
+ end
98
+
99
+ @thread.kill()
100
+ @thread = nil
101
+ end
102
+
103
+ # After calling on_request(), this can be called to halt the program's
104
+ # execution until the thread is stopped.
105
+ def wait()
106
+ if(@thread.nil?)
107
+ @logger.error("Tried to wait on a Nesser instance that wasn't listening!")
108
+ return
109
+ end
110
+
111
+ @thread.join()
112
+ end
113
+
114
+ # Send out a query, asynchronously. This immediately returns, then, when the
115
+ # query is finished, the callback block is called with a Nesser::Packet that
116
+ # represents the response (or nil, if there was a timeout).
117
+ def Nesser.query(s, hostname, params = {})
118
+ server = params[:server] || "8.8.8.8"
119
+ port = params[:port] || 53
120
+ type = params[:type] || Nesser::Packet::TYPE_A
121
+ cls = params[:cls] || Nesser::Packet::CLS_IN
122
+ timeout = params[:timeout] || 3
123
+
124
+ packet = Nesser::Packet.new(rand(65535), Nesser::Packet::QR_QUERY, Nesser::Packet::OPCODE_QUERY, Nesser::Packet::FLAG_RD, Nesser::Packet::RCODE_SUCCESS)
125
+ packet.add_question(Nesser::Packet::Question.new(hostname, type, cls))
126
+
127
+ s = UDPSocket.new()
128
+
129
+ return Thread.new() do
130
+ begin
131
+ s.send(packet.serialize(), 0, server, port)
132
+
133
+ timeout(timeout) do
134
+ response = s.recv(65536)
135
+ proc.call(Nesser::Packet.unpack(response))
136
+ end
137
+ rescue Timeout::Error
138
+ proc.call(nil)
139
+ rescue Exception => e
140
+ @logger.error("There was an exception sending a query for #{hostname} to #{server}:#{port}: #{e}")
141
+ ensure
142
+ if(s)
143
+ s.close()
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,18 @@
1
+ # Encoding: ASCII-8BIT
2
+ ##
3
+ # dns_exception.rb
4
+ # Created June 20, 2017
5
+ # By Ron Bowes
6
+ #
7
+ # See LICENSE.md
8
+ #
9
+ # Implements a simple exception class for dns errors.
10
+ ##
11
+
12
+ module Nesser
13
+ class DnsException < StandardError
14
+ end
15
+
16
+ class FormatException < DnsException
17
+ end
18
+ end
@@ -0,0 +1,79 @@
1
+ # Encoding: ASCII-8BIT
2
+ ##
3
+ # answer.rb
4
+ # Created June 20, 2017
5
+ # By Ron Bowes
6
+ #
7
+ # See: LICENSE.md
8
+ #
9
+ # A DNS answer. A DNS response packet contains zero or more Answer records
10
+ # (defined by the 'ancount' value in the header). An answer contains the
11
+ # name of the domain from the question, followed by a resource record.
12
+ ##
13
+
14
+ module Nesser
15
+ class Answer
16
+ attr_reader :name, :type, :cls, :ttl, :rr
17
+
18
+ def initialize(name:, type:, cls:, ttl:, rr:)
19
+ @name = name
20
+ @type = type
21
+ @cls = cls
22
+ @ttl = ttl
23
+ @rr = rr
24
+ end
25
+
26
+ def self.unpack(unpacker)
27
+ name = unpacker.unpack_name()
28
+ type, cls, ttl = unpacker.unpack("nnN")
29
+
30
+ case type
31
+ when TYPE_A
32
+ rr = A.unpack(unpacker)
33
+ when TYPE_NS
34
+ rr = NS.unpack(unpacker)
35
+ when TYPE_CNAME
36
+ rr = CNAME.unpack(unpacker)
37
+ when TYPE_SOA
38
+ rr = SOA.unpack(unpacker)
39
+ when TYPE_MX
40
+ rr = MX.unpack(unpacker)
41
+ when TYPE_TXT
42
+ rr = TXT.unpack(unpacker)
43
+ when TYPE_AAAA
44
+ rr = AAAA.unpack(unpacker)
45
+ else
46
+ rr = RRUnknown.unpack(unpacker, type)
47
+ end
48
+
49
+ return self.new(
50
+ name: name,
51
+ type: type,
52
+ cls: cls,
53
+ ttl: ttl,
54
+ rr: rr,
55
+ )
56
+ end
57
+
58
+ def pack(packer)
59
+ # The name is echoed back
60
+ packer.pack_name(@name)
61
+
62
+ # The type/class/ttl are added
63
+ packer.pack('nnN', @type, @cls, @ttl)
64
+
65
+ # Finally, the length and rr (the length is included)
66
+ @rr.pack(packer)
67
+ end
68
+
69
+ def to_s()
70
+ return '%s %d [%s %s] %s' % [
71
+ @name,
72
+ @ttl,
73
+ TYPES[@type] || '<0x%04x?>' % @type,
74
+ CLSES[@cls] || '<0x%04x?>' % @cls,
75
+ @rr.to_s(),
76
+ ]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,111 @@
1
+ # Encoding: ASCII-8BIT
2
+ ##
3
+ # constants.rb
4
+ # Created June 20, 2017
5
+ # By Ron Bowes
6
+ #
7
+ # See: LICENSE.md
8
+ ##
9
+
10
+ module Nesser
11
+ # Max recursion depth for parsing names
12
+ MAX_RECURSION_DEPTH = 16
13
+
14
+ # These restrictions are from RFC 952
15
+ LEGAL_CHARACTERS = (
16
+ ('a'..'z').to_a +
17
+ ('A'..'Z').to_a +
18
+ (0..9).to_a +
19
+ ['-', '.']
20
+ )
21
+ MAX_SEGMENT_LENGTH = 63
22
+ MAX_TOTAL_LENGTH = 253
23
+
24
+ CLS_IN = 0x0001 # Internet
25
+ CLSES = {
26
+ CLS_IN => "IN",
27
+ }
28
+
29
+ # Request / response
30
+ QR_QUERY = 0x0000
31
+ QR_RESPONSE = 0x0001
32
+
33
+ QRS = {
34
+ QR_QUERY => "QUERY",
35
+ QR_RESPONSE => "RESPONSE",
36
+ }
37
+
38
+ # Return codes
39
+ RCODE_SUCCESS = 0x0000
40
+ RCODE_FORMAT_ERROR = 0x0001
41
+ RCODE_SERVER_FAILURE = 0x0002 # :servfail
42
+ RCODE_NAME_ERROR = 0x0003 # :NXDomain
43
+ RCODE_NOT_IMPLEMENTED = 0x0004
44
+ RCODE_REFUSED = 0x0005
45
+
46
+ RCODES = {
47
+ RCODE_SUCCESS => ":NoError (RCODE_SUCCESS)",
48
+ RCODE_FORMAT_ERROR => ":FormErr (RCODE_FORMAT_ERROR)",
49
+ RCODE_SERVER_FAILURE => ":ServFail (RCODE_SERVER_FAILURE)",
50
+ RCODE_NAME_ERROR => ":NXDomain (RCODE_NAME_ERROR)",
51
+ RCODE_NOT_IMPLEMENTED => ":NotImp (RCODE_NOT_IMPLEMENTED)",
52
+ RCODE_REFUSED => ":Refused (RCODE_REFUSED)",
53
+ }
54
+
55
+ # Opcodes - only QUERY is typically used
56
+ OPCODE_QUERY = 0x0000
57
+ OPCODE_IQUERY = 0x0800
58
+ OPCODE_STATUS = 0x1000
59
+
60
+ OPCODES = {
61
+ OPCODE_QUERY => "OPCODE_QUERY",
62
+ OPCODE_IQUERY => "OPCODE_IQUERY",
63
+ OPCODE_STATUS => "OPCODE_STATUS",
64
+ }
65
+
66
+ # The types that we support
67
+ TYPE_A = 0x0001
68
+ TYPE_NS = 0x0002
69
+ TYPE_CNAME = 0x0005
70
+ TYPE_SOA = 0x0006
71
+ TYPE_MX = 0x000f
72
+ TYPE_TXT = 0x0010
73
+ TYPE_AAAA = 0x001c
74
+ TYPE_ANY = 0x00FF
75
+
76
+ TYPES = {
77
+ TYPE_A => "A",
78
+ TYPE_NS => "NS",
79
+ TYPE_CNAME => "CNAME",
80
+ TYPE_SOA => "SOA",
81
+ TYPE_MX => "MX",
82
+ TYPE_TXT => "TXT",
83
+ TYPE_AAAA => "AAAA",
84
+ TYPE_ANY => "ANY",
85
+ }
86
+
87
+ # The DNS flags
88
+ FLAG_AA = 0x0008 # Authoritative answer
89
+ FLAG_TC = 0x0004 # Truncated
90
+ FLAG_RD = 0x0002 # Recursion desired
91
+ FLAG_RA = 0x0001 # Recursion available
92
+
93
+ # This converts a set of flags, as an integer, into a string
94
+ def self.FLAGS(flags)
95
+ result = []
96
+ if((flags & FLAG_AA) == FLAG_AA)
97
+ result << "AA"
98
+ end
99
+ if((flags & FLAG_TC) == FLAG_TC)
100
+ result << "TC"
101
+ end
102
+ if((flags & FLAG_RD) == FLAG_RD)
103
+ result << "RD"
104
+ end
105
+ if((flags & FLAG_RA) == FLAG_RA)
106
+ result << "RA"
107
+ end
108
+
109
+ return result.join("|")
110
+ end
111
+ end