From 2c4eaff583a65ec9ad2f394bb87ca69cb1904d98 Mon Sep 17 00:00:00 2001 From: OJ Reeves Date: Wed, 30 Jul 2025 15:02:08 +1000 Subject: [PATCH] Support encoding/decoding of data from C2 profile --- lib/msf/core/payload/malleable_c2.rb | 58 +++++++++++++++++++++------- lib/rex/post/meterpreter/client.rb | 12 +++--- lib/rex/post/meterpreter/packet.rb | 18 +++++---- 3 files changed, 59 insertions(+), 29 deletions(-) diff --git a/lib/msf/core/payload/malleable_c2.rb b/lib/msf/core/payload/malleable_c2.rb index 396937e44b..f4b1f2f647 100644 --- a/lib/msf/core/payload/malleable_c2.rb +++ b/lib/msf/core/payload/malleable_c2.rb @@ -182,7 +182,19 @@ module Msf::Payload::MalleableC2 prefix = prepends.map {|p| p.args[0]}.join('') appends = self.http_get&.server&.output&.append || [] suffix = appends.map {|p| p.args[0]}.join('') - prefix + raw_bytes + suffix + + # do any encoding necessary + if raw_bytes.length > 0 + if self.http_get&.server&.output&.has_directive('base64') + raw_bytes = Rex::Text.encode_base64(raw_bytes) + elsif self.http_get&.server&.output&.has_directive('base64url') + raw_bytes = Rex::Text.encode_base64url(raw_bytes) + end + end + + result = prefix + raw_bytes + suffix + + result end def unwrap_inbound_post(raw_bytes) @@ -197,6 +209,15 @@ module Msf::Payload::MalleableC2 unless suffix.empty? || (raw_bytes[-suffix.length, raw_bytes.length] <=> suffix) != 0 raw_bytes = raw_bytes[0, raw_bytes.length - suffix.length] end + + # do any decoding necessary + if raw_bytes.length > 0 + if self.http_post&.client&.output&.has_directive('base64') + raw_bytes = Rex::Text.decode_base64(raw_bytes) + elsif self.http_post&.client&.output&.has_directive('base64url') + raw_bytes = Rex::Text.decode_base64url(raw_bytes) + end + end raw_bytes end @@ -213,15 +234,19 @@ module Msf::Payload::MalleableC2 self.add_http_tlv(get_uri, client, get_tlv) prepends = self.http_get&.server&.output&.prepend || [] - prefix = prepends.map {|p| p.args[0]}.join('') - get_tlv.add_tlv(MET::TLV_TYPE_C2_SKIP_COUNT, prefix.length) unless prefix.length == 0 + prefix_len = prepends.map {|p| p.args[0].length}.sum + get_tlv.add_tlv(MET::TLV_TYPE_C2_PREFIX_SKIP, prefix_len) unless prefix_len == 0 + + appends = self.http_get&.server&.output&.append || [] + suffix_len = appends.map {|s| s.args[0].length}.sum + get_tlv.add_tlv(MET::TLV_TYPE_C2_SUFFIX_SKIP, suffix_len) unless suffix_len == 0 client.get_section('metadata') {|meta| - enc_flags = 0 - enc_flags |= MET::C2_ENCODING_FLAG_B64 if meta.has_directive('base64') - enc_flags |= MET::C2_ENCODING_FLAG_B64URL if meta.has_directive('base64url') + enc_flags = MET::C2_ENCODING_NONE + enc_flags = MET::C2_ENCODING_B64URL if meta.has_directive('base64url') + enc_flags = MET::C2_ENCODING_B64 if meta.has_directive('base64') - get_tlv.add_tlv(MET::TLV_TYPE_C2_ENC, enc_flags) if enc_flags != 0 + get_tlv.add_tlv(MET::TLV_TYPE_C2_ENC, enc_flags) if enc_flags != MET::C2_ENCODING_NONE get_tlv.add_tlv(MET::TLV_TYPE_C2_UUID_GET, meta.get_directive('parameter')[0].args[0]) if meta.has_directive('parameter') get_tlv.add_tlv(MET::TLV_TYPE_C2_UUID_HEADER, meta.get_directive('header')[0].args[0]) if meta.has_directive('header') # assume uri-append for POST otherwise. @@ -237,16 +262,20 @@ module Msf::Payload::MalleableC2 http_post.get_section('client') {|client| self.add_http_tlv(post_uri, client, post_tlv) - prepends = self.http_get&.server&.output&.prepend || [] - prefix = prepends.map {|p| p.args[0]}.join('') - post_tlv.add_tlv(MET::TLV_TYPE_C2_SKIP_COUNT, prefix.length) unless prefix.length == 0 + prepends = self.http_post&.server&.output&.prepend || [] + prefix_len = prepends.map {|p| p.args[0].length}.sum + post_tlv.add_tlv(MET::TLV_TYPE_C2_PREFIX_SKIP, prefix_len) unless prefix_len == 0 + + appends = self.http_post&.server&.output&.append || [] + suffix_len = appends.map {|s| s.args[0].length}.sum + post_tlv.add_tlv(MET::TLV_TYPE_C2_SUFFIX_SKIP, suffix_len) unless suffix_len == 0 client.get_section('output') {|client_output| - enc_flags = 0 - enc_flags |= MET::C2_ENCODING_FLAG_B64 if client_output.has_directive('base64') - enc_flags |= MET::C2_ENCODING_FLAG_B64URL if client_output.has_directive('base64url') + enc_flags = MET::C2_ENCODING_NONE + enc_flags = MET::C2_ENCODING_B64URL if client_output.has_directive('base64url') + enc_flags = MET::C2_ENCODING_B64 if client_output.has_directive('base64') - post_tlv.add_tlv(MET::TLV_TYPE_C2_ENC, enc_flags) if enc_flags != 0 + post_tlv.add_tlv(MET::TLV_TYPE_C2_ENC, enc_flags) if enc_flags != MET::C2_ENCODING_NONE 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? @@ -258,7 +287,6 @@ module Msf::Payload::MalleableC2 post_tlv.add_tlv(MET::TLV_TYPE_C2_UUID_GET, client_id.get_directive('parameter')[0].args[0]) if client_id.has_directive('parameter') post_tlv.add_tlv(MET::TLV_TYPE_C2_UUID_HEADER, client_id.get_directive('header')[0].args[0]) if client_id.has_directive('header') # assume uri-append for POST otherwise given that we always put the TLV payload in the body? - # TODO: add support for adding a form rather than just a payload body? } } diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index edbf8886f9..17b71b7e95 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -117,20 +117,20 @@ class Client # # Wrap the given packet data with any prefixes and suffixes that are stored in - # the associated C2 profile server configuration (if it exists) + # the associated C2 profile server configuration (if it exists) and handle + # encoding of data # def wrap_packet(raw_bytes) - raw_bytes = self.c2_profile.wrap_outbound_get(raw_bytes) if self.c2_profile - raw_bytes + self.c2_profile.wrap_outbound_get(raw_bytes) if self.c2_profile end # # Unwrap the given packet data from any prefixes and suffixes that are stored in - # the associated C2 profile client configuration (if it exists) + # the associated C2 profile client configuration (if it exists) and handle + # decoding of data # def unwrap_packet(raw_bytes) - raw_bytes = self.c2_profile.unwrap_inbound_post(raw_bytes) if self.c2_profile - raw_bytes + self.c2_profile.unwrap_inbound_post(raw_bytes) if self.c2_profile end # diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index e182678073..42cc91195e 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -132,18 +132,20 @@ TLV_TYPE_C2_CERT_HASH = TLV_META_TYPE_RAW | 717 # Expected SSL certi 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_SKIP_COUNT = TLV_META_TYPE_UINT | 721 # Number of bytes of the incoming payload to ignore before parsing -TLV_TYPE_C2_UUID_COOKIE = TLV_META_TYPE_STRING | 722 # Name of the cookie to put the UUID in -TLV_TYPE_C2_UUID_GET = TLV_META_TYPE_STRING | 723 # Name of the GET parameter to put the UUID in -TLV_TYPE_C2_UUID_HEADER = TLV_META_TYPE_STRING | 724 # Name of the header to put the UUID in -TLV_TYPE_C2_UUID = TLV_META_TYPE_STRING | 725 # string representation of the UUID for C2s +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 # # C2 Encoding flags # -C2_ENCODING_FLAG_B64 = (1 << 0) # straight Base64 encoding -C2_ENCODING_FLAG_B64URL = (1 << 1) # encoding Base64 with URL-safe values -C2_ENCODING_FLAG_URL = (1 << 2) # straight URL encoding +C2_ENCODING_NONE = 0 # No encoding at all +C2_ENCODING_B64 = 1 # Base64 encoding +C2_ENCODING_B64URL = 2 # Base64 encoding with URI-safe characters +C2_ENCODING_URL = 3 # URL encoding # # Core flags