nesser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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