|
5 | 5 |
|
6 | 6 | module Msf
|
7 | 7 |
|
8 |
| -### |
9 |
| -# |
10 | 8 | # This mixin provides an interface to generating cmdstagers
|
11 |
| -# |
12 |
| -### |
13 | 9 | module Exploit::CmdStager
|
14 | 10 |
|
15 | 11 | include Msf::Exploit::EXE
|
16 | 12 |
|
| 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. |
17 | 41 | #
|
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. |
20 | 44 | def initialize(info = {})
|
21 | 45 | 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) |
24 | 56 | end
|
25 | 57 |
|
26 | 58 |
|
| 59 | + # Executes the command stager while showing the progress. This method should |
| 60 | + # be called from exploits using this mixin. |
27 | 61 | #
|
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] |
30 | 68 | def execute_cmdstager(opts = {})
|
31 |
| - cmd_list = generate_cmdstager(opts) |
| 69 | + self.cmd_list = generate_cmdstager(opts) |
32 | 70 |
|
33 |
| - execute_cmdstager_begin(opts) |
| 71 | + stager_instance.setup(self) |
34 | 72 |
|
35 |
| - sent = 0 |
36 |
| - total_bytes = 0 |
37 |
| - cmd_list.each { |cmd| total_bytes += cmd.length } |
| 73 | + begin |
| 74 | + execute_cmdstager_begin(opts) |
38 | 75 |
|
39 |
| - delay = opts[:delay] |
40 |
| - delay ||= 0.25 |
| 76 | + sent = 0 |
| 77 | + total_bytes = 0 |
| 78 | + cmd_list.each { |cmd| total_bytes += cmd.length } |
41 | 79 |
|
42 |
| - cmd_list.each do |cmd| |
43 |
| - execute_command(cmd, opts) |
44 |
| - sent += cmd.length |
| 80 | + delay = opts[:delay] |
| 81 | + delay ||= 0.25 |
45 | 82 |
|
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 |
49 | 86 |
|
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 |
52 | 93 |
|
53 |
| - execute_cmdstager_end(opts) |
| 94 | + execute_cmdstager_end(opts) |
| 95 | + ensure |
| 96 | + stager_instance.teardown(self) |
| 97 | + end |
54 | 98 | end
|
55 | 99 |
|
56 | 100 |
|
57 |
| - # |
58 | 101 | # Generates a cmd stub based on the current target's architecture
|
59 |
| - # and operating system. |
| 102 | + # and platform. |
60 | 103 | #
|
| 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 |
61 | 111 | def generate_cmdstager(opts = {}, pl = nil)
|
62 |
| - pl ||= payload.encoded |
| 112 | + select_cmdstager(opts) |
63 | 113 |
|
64 |
| - @exe = generate_payload_exe |
| 114 | + self.exe = generate_payload_exe(:code => pl) |
65 | 115 |
|
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)) |
68 | 118 |
|
69 |
| - if (cmd_list.nil? or cmd_list.length < 1) |
| 119 | + if (cmd_list.nil? || cmd_list.length < 1) |
70 | 120 | print_error("The command stager could not be generated")
|
71 | 121 | raise ArgumentError
|
72 | 122 | end
|
73 | 123 |
|
74 |
| - @cmd_list = cmd_list |
| 124 | + cmd_list |
75 | 125 | end
|
76 | 126 |
|
77 |
| - |
78 |
| - # |
79 |
| - # Show the progress of the upload |
| 127 | + # Show the progress of the upload while cmd staging |
80 | 128 | #
|
| 129 | + # @param total [Float] The total number of bytes to send |
| 130 | + # @param sent [Float] The number of bytes sent |
| 131 | + # @return [void] |
81 | 132 | def progress(total, sent)
|
82 | 133 | done = (sent.to_f / total.to_f) * 100
|
83 | 134 | percent = "%3.2f%%" % done.to_f
|
84 | 135 | print_status("Command Stager progress - %7s done (%d/%d bytes)" % [percent, sent, total])
|
85 | 136 | end
|
86 | 137 |
|
| 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. |
87 | 268 | #
|
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. |
89 | 298 | #
|
| 299 | + # @param opts [Hash] Hash of configuration options. |
90 | 300 | def execute_cmdstager_begin(opts)
|
91 | 301 | 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. |
92 | 307 | def execute_cmdstager_end(opts)
|
93 | 308 | end
|
94 | 309 |
|
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 |
96 | 317 |
|
97 | 318 | end
|
| 319 | +end |
0 commit comments