Skip to content

Commit 51695c4

Browse files
committed
Land rapid7#2484, @zeroSteiner's refactoring for CmdStager
2 parents b15297e + c041682 commit 51695c4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1145
-507
lines changed

lib/msf/core/exploit/cmdstager.rb

Lines changed: 261 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,315 @@
55

66
module Msf
77

8-
###
9-
#
108
# This mixin provides an interface to generating cmdstagers
11-
#
12-
###
139
module Exploit::CmdStager
1410

1511
include Msf::Exploit::EXE
1612

13+
# Constant for stagers - used when creating an stager instance.
14+
STAGERS = {
15+
:bourne => Rex::Exploitation::CmdStagerBourne,
16+
:debug_asm => Rex::Exploitation::CmdStagerDebugAsm,
17+
:debug_write => Rex::Exploitation::CmdStagerDebugWrite,
18+
:echo => Rex::Exploitation::CmdStagerEcho,
19+
:printf => Rex::Exploitation::CmdStagerPrintf,
20+
:vbs => Rex::Exploitation::CmdStagerVBS,
21+
:vbs_adodb => Rex::Exploitation::CmdStagerVBS,
22+
:tftp => Rex::Exploitation::CmdStagerTFTP
23+
}
24+
25+
# Constant for decoders - used when checking the default flavor decoder.
26+
DECODERS = {
27+
:debug_asm => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "debug_asm"),
28+
:debug_write => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "debug_write"),
29+
:vbs => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64"),
30+
:vbs_adodb => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64_adodb")
31+
}
32+
33+
attr_accessor :stager_instance
34+
attr_accessor :cmd_list
35+
attr_accessor :flavor
36+
attr_accessor :decoder
37+
attr_accessor :exe
38+
39+
# Creates an instance of an exploit that uses an CMD Stager and register the
40+
# datastore options provided by the mixin.
1741
#
18-
# Creates an instance of an exploit that uses an CmdStager overwrite.
19-
#
42+
# @param info [Hash] Hash containing information to initialize the exploit.
43+
# @return [Msf::Module::Exploit] the exploit module.
2044
def initialize(info = {})
2145
super
22-
@cmd_list = nil
23-
@stager_instance = nil
46+
47+
flavors = module_flavors
48+
flavors = STAGERS.keys if flavors.empty?
49+
flavors.unshift('auto')
50+
51+
register_advanced_options(
52+
[
53+
OptEnum.new('CMDSTAGER::FLAVOR', [false, 'The CMD Stager to use.', 'auto', flavors]),
54+
OptString.new('CMDSTAGER::DECODER', [false, 'The decoder stub to use.'])
55+
], self.class)
2456
end
2557

2658

59+
# Executes the command stager while showing the progress. This method should
60+
# be called from exploits using this mixin.
2761
#
28-
# Execute the command stager while showing the progress
29-
#
62+
# @param opts [Hash] Hash containing configuration options. Also allow to
63+
# send opts to the Rex::Exploitation::CmdStagerBase constructor.
64+
# @option opts :flavor [Symbol] The CMD Stager to use.
65+
# @option opts :decoder [Symbol] The decoder stub to use.
66+
# @option opts :delay [Float] Delay between command executions.
67+
# @return [void]
3068
def execute_cmdstager(opts = {})
31-
cmd_list = generate_cmdstager(opts)
69+
self.cmd_list = generate_cmdstager(opts)
3270

33-
execute_cmdstager_begin(opts)
71+
stager_instance.setup(self)
3472

35-
sent = 0
36-
total_bytes = 0
37-
cmd_list.each { |cmd| total_bytes += cmd.length }
73+
begin
74+
execute_cmdstager_begin(opts)
3875

39-
delay = opts[:delay]
40-
delay ||= 0.25
76+
sent = 0
77+
total_bytes = 0
78+
cmd_list.each { |cmd| total_bytes += cmd.length }
4179

42-
cmd_list.each do |cmd|
43-
execute_command(cmd, opts)
44-
sent += cmd.length
80+
delay = opts[:delay]
81+
delay ||= 0.25
4582

46-
# In cases where a server has multiple threads, we want to be sure that
47-
# commands we execute happen in the correct (serial) order.
48-
::IO.select(nil, nil, nil, delay)
83+
cmd_list.each do |cmd|
84+
execute_command(cmd, opts)
85+
sent += cmd.length
4986

50-
progress(total_bytes, sent)
51-
end
87+
# In cases where a server has multiple threads, we want to be sure that
88+
# commands we execute happen in the correct (serial) order.
89+
::IO.select(nil, nil, nil, delay)
90+
91+
progress(total_bytes, sent)
92+
end
5293

53-
execute_cmdstager_end(opts)
94+
execute_cmdstager_end(opts)
95+
ensure
96+
stager_instance.teardown(self)
97+
end
5498
end
5599

56100

57-
#
58101
# Generates a cmd stub based on the current target's architecture
59-
# and operating system.
102+
# and platform.
60103
#
104+
# @param opts [Hash] Hash containing configuration options. Also allow to
105+
# send opts to the Rex::Exploitation::CmdStagerBase constructor.
106+
# @option opts :flavor [Symbol] The CMD Stager to use.
107+
# @option opts :decoder [Symbol] The decoder stub to use.
108+
# @param pl [String] String containing the payload to execute
109+
# @return [Array] The list of commands to execute
110+
# @raise [ArgumentError] raised if the cmd stub can not be generated
61111
def generate_cmdstager(opts = {}, pl = nil)
62-
pl ||= payload.encoded
112+
select_cmdstager(opts)
63113

64-
@exe = generate_payload_exe
114+
self.exe = generate_payload_exe(:code => pl)
65115

66-
@stager_instance = create_stager(@exe)
67-
cmd_list = @stager_instance.generate(opts)
116+
self.stager_instance = create_stager
117+
cmd_list = stager_instance.generate(opts_with_decoder(opts))
68118

69-
if (cmd_list.nil? or cmd_list.length < 1)
119+
if (cmd_list.nil? || cmd_list.length < 1)
70120
print_error("The command stager could not be generated")
71121
raise ArgumentError
72122
end
73123

74-
@cmd_list = cmd_list
124+
cmd_list
75125
end
76126

77-
78-
#
79-
# Show the progress of the upload
127+
# Show the progress of the upload while cmd staging
80128
#
129+
# @param total [Float] The total number of bytes to send
130+
# @param sent [Float] The number of bytes sent
131+
# @return [void]
81132
def progress(total, sent)
82133
done = (sent.to_f / total.to_f) * 100
83134
percent = "%3.2f%%" % done.to_f
84135
print_status("Command Stager progress - %7s done (%d/%d bytes)" % [percent, sent, total])
85136
end
86137

138+
# Selects the correct cmd stager and decoder stub to use
139+
#
140+
# @param opts [Hash] Hash containing the options to select te correct cmd
141+
# stager and decoder.
142+
# @option opts :flavor [Symbol] The cmd stager to use.
143+
# @option opts :decoder [Symbol] The decoder stub to use.
144+
# @return [void]
145+
# @raise [ArgumentError] raised if a cmd stager can not be selected or it
146+
# isn't compatible with the target platform.
147+
def select_cmdstager(opts = {})
148+
self.flavor = select_flavor(opts)
149+
raise ArgumentError, "Unable to select CMD Stager" if flavor.nil?
150+
raise ArgumentError, "The CMD Stager '#{flavor}' isn't compatible with the target" unless compatible_flavor?(flavor)
151+
self.decoder = select_decoder(opts)
152+
end
153+
154+
155+
# Returns a hash with the :decoder option if possible
156+
#
157+
# @params opts [Hash] Input Hash.
158+
# @return [Hash] Hash with the input data and a :decoder option when
159+
# possible.
160+
def opts_with_decoder(opts = {})
161+
return opts if opts.include?(:decoder)
162+
return opts.merge(:decoder => decoder) if decoder
163+
opts
164+
end
165+
166+
167+
# Create an instance of the flavored stager.
168+
#
169+
# @return [Rex::Exploitation::CmdStagerBase] The cmd stager to use.
170+
# @raise [NoMethodError] raised if the flavor doesn't exist.
171+
def create_stager
172+
STAGERS[flavor].new(exe)
173+
end
174+
175+
# Returns the default decoder stub for the input flavor.
176+
#
177+
# @param f [Symbol] the input flavor.
178+
# @return [Symbol] the decoder.
179+
# @return [nil] if there isn't a default decoder to use for the current
180+
# cmd stager flavor.
181+
def default_decoder(f)
182+
DECODERS[f]
183+
end
184+
185+
# Selects the correct cmd stager decoder to use based on three rules: (1) use
186+
# the decoder provided in input options, (2) use the decoder provided by the
187+
# user through datastore options, (3) select the default decoder for the
188+
# current cmd stager flavor if available.
189+
#
190+
# @param opts [Hash] Hash containing the options to select te correct
191+
# decoder.
192+
# @option opts :decoder [String] The decoder stub to use.
193+
# @return [String] The decoder.
194+
# @return [nil] if a decoder can not be selected.
195+
def select_decoder(opts = {})
196+
return opts[:decoder] if opts.include?(:decoder)
197+
return datastore['CMDSTAGER::DECODER'] unless datastore['CMDSTAGER::DECODER'].blank?
198+
default_decoder(flavor)
199+
end
200+
201+
# Selects the correct cmd stager to use based on three rules: (1) use the
202+
# flavor provided in options, (2) use the flavor provided by the user
203+
# through datastore options, (3) guess the flavor using the target platform.
204+
#
205+
# @param opts [Hash] Hash containing the options to select te correct cmd
206+
# stager
207+
# @option opts :flavor [Symbol] The cmd stager flavor to use.
208+
# @return [Symbol] The flavor to use.
209+
# @return [nil] if a flavor can not be selected.
210+
def select_flavor(opts = {})
211+
return opts[:flavor].to_sym if opts.include?(:flavor)
212+
unless datastore['CMDSTAGER::FLAVOR'].blank? or datastore['CMDSTAGER::FLAVOR'] == 'auto'
213+
return datastore['CMDSTAGER::FLAVOR'].to_sym
214+
end
215+
guess_flavor
216+
end
217+
218+
# Guess the cmd stager flavor to use using information from the module,
219+
# target or platform.
220+
#
221+
# @return [Symbol] The cmd stager flavor to use.
222+
# @return [nil] if the cmd stager flavor can not be guessed.
223+
def guess_flavor
224+
# First try to guess a compatible flavor based on the module & target information.
225+
unless target_flavor.nil?
226+
case target_flavor.class.to_s
227+
when 'Array'
228+
return target_flavor[0].to_sym
229+
when 'String'
230+
return target_flavor.to_sym
231+
when 'Symbol'
232+
return target_flavor
233+
end
234+
end
235+
236+
# Second try to guess a compatible flavor based on the target platform.
237+
return nil unless target_platform.names.length == 1
238+
c_platform = target_platform.names.first
239+
case c_platform
240+
when /linux/i
241+
:bourne
242+
when /osx/i
243+
:bourne
244+
when /unix/i
245+
:bourne
246+
when /win/i
247+
:vbs
248+
else
249+
nil
250+
end
251+
end
252+
253+
# Returns all the compatible stager flavors specified by the module and each
254+
# of it's targets.
255+
#
256+
# @return [Array] the list of all compatible cmd stager flavors.
257+
def module_flavors
258+
flavors = []
259+
flavors += Array(module_info['CmdStagerFlavor']) if module_info['CmdStagerFlavor']
260+
targets.each do |target|
261+
flavors += Array(target.opts['CmdStagerFlavor']) if target.opts['CmdStagerFlavor']
262+
end
263+
flavors.uniq!
264+
flavors.map { |flavor| flavor.to_s }
265+
end
266+
267+
# Returns the compatible stager flavors for the current target or module.
87268
#
88-
# Methods to override - not used internally
269+
# @return [Array] the list of compatible cmd stager flavors.
270+
# @return [Symbol] the compatible cmd stager flavor.
271+
# @return [String] the compatible cmd stager flavor.
272+
# @return [nil] if there isn't any compatible flavor defined.
273+
def target_flavor
274+
return target.opts['CmdStagerFlavor'] if target && target.opts['CmdStagerFlavor']
275+
return module_info['CmdStagerFlavor'] if module_info['CmdStagerFlavor']
276+
nil
277+
end
278+
279+
# Answers if the input flavor is compatible with the current target or module.
280+
#
281+
# @param f [Symbol] The flavor to check
282+
# @returns [Boolean] true if compatible, false otherwise.
283+
def compatible_flavor?(f)
284+
return true if target_flavor.nil?
285+
case target_flavor.class.to_s
286+
when 'String'
287+
return true if target_flavor == f.to_s
288+
when 'Array'
289+
target_flavor.each { |tr| return true if tr.to_sym == f }
290+
when 'Symbol'
291+
return true if target_flavor == f
292+
end
293+
false
294+
end
295+
296+
# Code to execute before the cmd stager stub. This method is designed to be
297+
# overriden by a module this mixin.
89298
#
299+
# @param opts [Hash] Hash of configuration options.
90300
def execute_cmdstager_begin(opts)
91301
end
302+
303+
# Code to execute after the cmd stager stub. This method is designed to be
304+
# overriden by a module this mixin.
305+
#
306+
# @param opts [Hash] Hash of configuration options.
92307
def execute_cmdstager_end(opts)
93308
end
94309

95-
end
310+
# Code to execute each command from the. This method is designed to be
311+
# overriden by a module using this mixin.
312+
#
313+
# @param opts [Hash] Hash of configuration options.
314+
def execute_command(cmd, opts)
315+
raise NotImplementedError
316+
end
96317

97318
end
319+
end

lib/msf/core/exploit/cmdstager_bourne.rb

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)