Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions lib/msf/core/handler/reverse_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,28 +180,22 @@ def scheme
end

def construct_luri(base_uri)
return nil unless base_uri

if base_uri && base_uri.length > 0
# strip trailing slashes
while base_uri[-1, 1] == '/'
base_uri = base_uri[0...-1]
end

# make sure the luri has the prefix
if base_uri[0, 1] != '/'
base_uri = "/#{base_uri}"
end
u = base_uri.dup

while u[-1] == '/'
u.chop!
end

base_uri.dup
u
end

# The local URI for the handler.
#
# @return [String] Representation of the URI to listen on.
def luri
construct_luri(datastore['LURI'] || "")
construct_luri(datastore['LURI'] || '')
end

def all_uris
Expand Down Expand Up @@ -286,17 +280,17 @@ def setup_handler
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
cid = request.qstring[directive[0].args[0]] if 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
cid = request.headers[directive[0].args[0]] if directive&.length > 0
end
else
directive = self.c2_profile&.http_get&.client&.metadata&.parameter
cid = request.qstring[directive[0].args[0]] if directive && directive.length > 0
cid = request.qstring[directive[0].args[0]] if directive&.length > 0
unless cid
directive = self.c2_profile&.http_get&.client&.metadata&.header
cid = request.headers[directive[0].args[0]] if directive && directive.length > 0
cid = request.headers[directive[0].args[0]] if directive&.length > 0
end
end

Expand Down
37 changes: 21 additions & 16 deletions lib/msf/core/payload/malleable_c2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@
##
# This module contains helper functions for parsing and loading malleable
# C2 profiles into ruby objects.
#
# See https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/malleable-c2_main.htm
##

require 'strscan'
require 'rex/post/meterpreter/packet'

# Handle escape sequences in the strings provided by the c2 profile
class String
def from_c2_string_value
module Msf::Payload::MalleableC2

MET = Rex::Post::Meterpreter
MC2 = Msf::Payload::MalleableC2

# Handle escape sequences in the strings provided by the c2 profile
def self.from_c2_string_value(s)
# Support substitution of a subset of escape characters:
# \r, \t, \n, \\, \x..
# Not supporting \u at this point.
# We do in a single regex and parse each as we go, as this avoids the
# potential for double-encoding.
self.gsub(/\\(x(..)|r|n|t|\\)/) {|b|
s.gsub(/\\(x(..)|r|n|t|\\)/) {|b|
case b[1]
when 'x'
[b[2, 4].to_i(16)].pack('C')
Expand All @@ -31,11 +37,6 @@ def from_c2_string_value
end
}
end
end

module Msf::Payload::MalleableC2

MET = Rex::Post::Meterpreter

class Token
attr_reader :type, :value
Expand Down Expand Up @@ -95,13 +96,15 @@ class Lexer

def initialize(file)
@tokens = []
tokenize(File.read(file))
tokenize(File.binread(file))
end

def is_block_keyword?(word)
BLOCK_KEYWORDS.include?(word)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a blocker: Introducing constants for the regex union like BLOCK_KEYWORDS = /#Regexp.union(BLOCK_KEYWORDS.sort)}\b/ as well as for OTHER_KEYWORDS might be nice to help align the approach taken for finding these lexemes (i.e. since there's is_block_keyword? here, but below you directly use type = BLOCK_KEYWORDS.union(OTHER_KEYWORDS).include?(word) ? :keyword : :identifier too)

end

private

def tokenize(text)
scanner = StringScanner.new(text)

Expand All @@ -113,7 +116,6 @@ def tokenize(text)
# comment
next
elsif scanner.scan(/\"(\\.|[^"])*\"/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "" an acceptable token?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. I guess so? though I can't see where we would want to specify that somewhere and it still have meaning.

#@tokens << Token.new(:string, scanner.matched[1..-2])
@tokens << Token.new(:string, scanner.matched[1..-2])
elsif scanner.scan(/[a-zA-Z0-9_\-\.\/]+/)
word = scanner.matched
Expand All @@ -122,7 +124,10 @@ def tokenize(text)
elsif scanner.scan(/[{};]/)
@tokens << Token.new(:symbol, scanner.matched)
else
raise "Unexpected token near: #{scanner.peek(20)}"
preceding_lines = scanner.string[0..scanner.pos].split("\n")
row = preceding_lines.length
col = preceding_lines.last&.size || 1
raise "Unexpected token near #{row}:#{col}: #{scanner.peek(20).split("\n").first}"
end
end
end
Expand Down Expand Up @@ -243,7 +248,7 @@ def to_tlv

post_tlv.add_tlv(MET::TLV_TYPE_C2_ENC, enc_flags) if enc_flags != 0

prepend_data = client_output.get_directive('prepend').map{|d|d.args[0]}.("")
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?
Expand Down Expand Up @@ -292,7 +297,7 @@ class ParsedSet
attr_accessor :key, :value
def initialize(key, value)
@key = key.downcase
@value = value.from_c2_string_value
@value = MC2.from_c2_string_value(value)
end
end

Expand Down Expand Up @@ -340,7 +345,7 @@ class ParsedDirective
attr_accessor :type, :args
def initialize(type, args)
@type = type.downcase
@args = args.map {|a| a.from_c2_string_value}
@args = args.map {|a| MC2.from_c2_string_value(a)}
end
end

Expand All @@ -362,7 +367,7 @@ def parse(file)
elsif current_token.type == :keyword && @lexer.is_block_keyword?(current_token.value)
profile.sections << parse_section
else
raise "Unexpected token at tope level: #{current_token.type}=#{current_token.value}"
raise "Unexpected token at top level: #{current_token.type}=#{current_token.value}"
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def initialize(info = {})
)

register_options([
OptString.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']),
OptPath.new('MALLEABLEC2', [false, 'Path to a file containing the malleable C2 profile']),
OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']),
OptString.new('EXTINIT', [false, 'Initialization strings for extensions'])
])
Expand Down
Loading