-
Notifications
You must be signed in to change notification settings - Fork 609
/
dnscat2.rb
executable file
·217 lines (178 loc) · 8.3 KB
/
dnscat2.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
##
# dnscat2_server.rb
# Created March, 2013
# By Ron Bowes
#
# See: LICENSE.md
#
# Implements basically the full Dnscat2 protocol. Doesn't care about
# lower-level protocols.
##
$LOAD_PATH << File.dirname(__FILE__) # A hack to make this work on 1.8/1.9
trap "SIGINT" do
puts "Ctrl-C is disabled, use exit"
end
# Create the window right away so other includes can create their own windows if they want
require 'libs/swindow'
WINDOW = SWindow.new(nil, true, { :prompt => "dnscat2> ", :name => "main" })
require 'controller/controller'
require 'libs/command_helpers'
require 'libs/settings'
require 'tunnel_drivers/driver_dns'
require 'tunnel_drivers/driver_tcp'
require 'tunnel_drivers/tunnel_drivers'
# Option parsing
require 'trollop'
require 'securerandom'
# version info
NAME = "dnscat2"
VERSION = "0.07"
# Don't ignore unhandled errors in threads
Thread.abort_on_exception = true
# Options
opts = Trollop::options do
version(NAME + " v" + VERSION + " (server)")
banner("You'll almost certainly want to run this in one of a few ways...")
banner("")
banner("Default host (0.0.0.0) and port (53), with no specific domain:")
banner("# ruby dnscat2.rb")
banner("")
banner("Default host/port, with a particular domain to listen on:")
banner("# ruby dnscat2.rb domain.com")
banner("")
banner("Or multiple domains:")
banner("# ruby dnscat2.rb a.com b.com c.com")
banner("")
banner("If you need to change the address or port it's listening on, that")
banner("can be done by passing the --dns argument:")
banner("# ruby dnscat2.rb --dns 'host=127.0.0.1,port=53531,domain=a.com,domain=b.com'")
banner("")
banner("For other options, see below!")
banner("")
opt :h, "Placeholder for help", :type => :boolean, :default => false
opt :version, "Get the dnscat version", :type => :boolean, :default => false
opt :dns, "Start a DNS server. Can optionally pass a number of comma-separated name=value pairs (host, port, domain). Eg, '--dns host=0.0.0.0,port=53531,domain=skullseclabs.org' - 'domain' can be passed multiple times",
:type => :string, :default => nil
opt :dnshost, "The DNS ip address to listen on [deprecated]",
:type => :string, :default => "0.0.0.0"
opt :dnsport, "The DNS port to listen on [deprecated]",
:type => :integer, :default => 53
opt :passthrough, "Unhandled requests are sent upstream DNS server, host:port",
:type => :string, :default => ""
opt :security, "Set the security level; 'open' lets the client choose; 'encrypted' requires encryption (default if --secret isn't set); 'authenticated' requires encryption and authentication (default if --secret is set)",
:type => :string, :default => nil
opt :secret, "A pre-shared secret, passed to both the client and server to prevent man-in-the-middle attacks",
:type => :string, :default => nil
opt :auto_command, "Send this to each client that connects",
:type => :string, :default => ""
opt :auto_attach, "Automatically attach to new sessions",
:type => :boolean, :default => false
opt :packet_trace, "Display incoming/outgoing dnscat packets",
:type => :boolean, :default => false
opt :process, "If set, the given process is run for every incoming console/exec session and given stdin/stdout. This has security implications.",
:type => :string, :default => nil
opt :history_size, "The number of lines of history that windows will maintain",
:type => :integer, :default => 1000
opt :listener, "DEBUG: Start a listener driver on the given port",
:type => :integer, :default => nil
opt :firehose, "If set, all output goes to stdout instead of being put in windows.",
:type => :boolean, :default => false
opt :cache, "If set, caching is enabled on the server.",
:type => :boolean, :default => true
end
SWindow.set_firehose(opts[:firehose])
if(opts[:security].nil?)
if(opts[:secret].nil?)
opts[:security] = 'encrypted'
else
opts[:security] = 'authenticated'
end
end
if(opts[:secret].nil?)
opts[:secret] = SecureRandom::hex(16)
end
WINDOW.puts("Welcome to dnscat2! Some documentation may be out of date.")
WINDOW.puts()
if(opts[:h])
WINDOW.puts("To get help, you have to use --help; I can't find any way to make")
WINDOW.puts("'-h' work on my command parser... :(")
exit
end
controller = Controller.new()
begin
Settings::GLOBAL.create("packet_trace", Settings::TYPE_BOOLEAN, opts[:packet_trace], "If set to 'true', will open some extra windows that will display incoming/outgoing dnscat2 packets, and also parsed command packets for command sessions.") do |old_val, new_val|
# We don't have any callbacks
end
Settings::GLOBAL.create("passthrough", Settings::TYPE_BLANK_IS_NIL, opts[:passthrough], "Send queries to the given upstream host (note: this can cause weird recursion problems). Expected: 'set passthrough host:port'. Set to blank to disable.") do |old_val, new_val|
if(new_val.nil?)
WINDOW.puts("passthrough => disabled")
DriverDNS.set_passthrough(nil, nil)
next
end
host, port = new_val.split(/:/, 2)
port = port || 53
DriverDNS.set_passthrough(host, port)
WINDOW.puts("passthrough => #{host}:#{port}")
end
Settings::GLOBAL.create("auto_attach", Settings::TYPE_BOOLEAN, opts[:auto_attach], "If true, the UI will automatically open new sessions") do |old_val, new_val|
WINDOW.puts("auto_attach => #{new_val}")
end
Settings::GLOBAL.create("auto_command", Settings::TYPE_BLANK_IS_NIL, opts[:auto_command], "The command (or semicolon-separated list of commands) will automatically be executed for each new session as if they were typed at the keyboard.") do |old_val, new_val|
WINDOW.puts("auto_command => #{new_val}")
end
Settings::GLOBAL.create("process", Settings::TYPE_BLANK_IS_NIL, opts[:process] || "", "If set, this process is spawned for each new console session ('--console' on the client), and it handles the session instead of getting the i/o from the keyboard.") do |old_val, new_val|
WINDOW.puts("process => #{new_val}")
end
Settings::GLOBAL.create("history_size", Settings::TYPE_INTEGER, opts[:history_size], "Change the number of lines to store in the new windows' histories") do |old_val, new_val|
SWindow.history_size = new_val
WINDOW.puts("history_size (for new windows) => #{new_val}")
end
Settings::GLOBAL.create("security", Settings::TYPE_STRING, opts[:security], "Options: 'open' (let the client decide), 'encrypted' (require clients to encrypt), 'authenticated' (require clients to authenticate)") do |old_val, new_val|
options = {
'open' => "Client can decide on security level",
'encrypted' => "All connections must be encrypted",
'authenticated' => "All connections must be encrypted and authenticated",
}
new_val_str = options[new_val]
if(!new_val_str)
raise(Settings::ValidationError, "Valid options for security: #{options.keys.map() { |value| "'#{value}'" }.join(', ')}")
end
WINDOW.puts("Security policy changed: #{new_val_str}")
end
Settings::GLOBAL.create("secret", Settings::TYPE_STRING, opts[:secret], "Pass the same --secret value to the client and the server for extra security") do |old_val, new_val|
end
rescue Settings::ValidationError => e
WINDOW.puts("There was an error with one of your commandline arguments:")
WINDOW.puts(e)
WINDOW.puts()
Trollop::die("Check your command-line arguments")
end
domains = []
if(opts[:dns])
begin
dns_settings = CommandHelpers.parse_setting_string(opts[:dns], { :host => "0.0.0.0", :port => "53", :domains => [], :domain => [] })
dns_settings[:domains] = dns_settings[:domain] + dns_settings[:domains]
rescue ArgumentError => e
WINDOW.puts("Sorry, we had trouble parsing your --dns string:")
WINDOW.puts(e)
exit(1)
end
elsif(opts[:dnsport] || opts[:dnshost])
# This way of starting a server is deprecated, technically
dns_settings = {
:host => opts[:dnshost],
:port => opts[:dnsport],
:domains => [],
}
end
# Add any domains passed on the commandline
dns_settings[:domains] = dns_settings[:domains] || []
dns_settings[:domains] += ARGV
# Start the DNS driver
TunnelDrivers.start({
:controller => controller,
:driver => DriverDNS,
:args => [dns_settings[:host], dns_settings[:port], dns_settings[:domains], opts[:cache]],
})
# Wait for the input window to finish its thing
SWindow.wait()