First working packet pivot session!

bug/bundler_fix
OJ 2017-07-16 19:32:58 +10:00 committed by Brent Cook
parent e3de01219a
commit fdc9864b61
12 changed files with 202 additions and 124 deletions

View File

@ -45,6 +45,7 @@ class Meterpreter < Rex::Post::Meterpreter::Client
# that is to be used as the client's connection to the server.
#
def initialize(rstream, opts={})
STDERR.puts("init in meterpreter\n")
super
opts[:capabilities] = {
@ -112,6 +113,77 @@ class Meterpreter < Rex::Post::Meterpreter::Client
end
def bootstrap(datastore={})
session = self
init_session = Proc.new do
# Configure unicode encoding before loading stdapi
session.encode_unicode = datastore['EnableUnicodeEncoding']
session.init_ui(self.user_input, self.user_output)
session.tlv_enc_key = session.core.negotiate_tlv_encryption
unless datastore['AutoVerifySession'] == false
unless session.is_valid_session?(datastore['AutoVerifySessionTimeout'].to_i)
print_error("Meterpreter session #{session.sid} is not valid and will be closed")
# Terminate the session without cleanup if it did not validate
session.skip_cleanup = true
session.kill
return nil
end
end
# always make sure that the new session has a new guid if it's not already known
guid = session.session_guid
if guid == "\x00" * 16
guid = [SecureRandom.uuid.gsub(/-/, '')].pack('H*')
session.core.set_session_guid(guid)
session.session_guid = guid
# TODO: New statgeless session, do some account in the DB so we can track it later.
else
# TODO: This session was either staged or previously known, and so we shold do some accounting here!
end
unless datastore['AutoLoadStdapi'] == false
session.load_stdapi
unless datastore['AutoSystemInfo'] == false
session.load_session_info
end
# only load priv on native windows
# TODO: abastrct this too, to remove windows stuff
if session.platform == 'windows' && [ARCH_X86, ARCH_X64].include?(session.arch)
session.load_priv rescue nil
end
end
# TODO: abstract this a little, perhaps a "post load" function that removes
# platform-specific stuff?
if session.platform == 'android'
session.load_android
end
['InitialAutoRunScript', 'AutoRunScript'].each do |key|
unless datastore[key].empty?
args = Shellwords.shellwords(datastore[key])
print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")
session.execute_script(args.shift, *args)
end
end
# Process the auto-run scripts for this session
if self.respond_to?('process_autoruns')
self.process_autoruns(datastore)
end
end
# Defer the session initialization to the Session Manager scheduler
framework.sessions.schedule init_session
end
##
# :category: Msf::Session::Provider::SingleCommandShell implementors
#

View File

@ -26,75 +26,6 @@ module MeterpreterOptions
], self.class)
end
#
# Once a session is created, automatically load the stdapi extension if the
# advanced option is set to true.
#
def on_session(session)
init_session = Proc.new do
# Configure unicode encoding before loading stdapi
session.encode_unicode = datastore['EnableUnicodeEncoding']
session.init_ui(self.user_input, self.user_output)
print_good("negotiating tlv encryption")
session.tlv_enc_key = session.core.negotiate_tlv_encryption
print_good("negotiated tlv encryption")
if datastore['AutoVerifySession']
if !session.is_valid_session?(datastore['AutoVerifySessionTimeout'].to_i)
print_error("Meterpreter session #{session.sid} is not valid and will be closed")
# Terminate the session without cleanup if it did not validate
session.skip_cleanup = true
session.kill
return nil
end
end
print_good("negotiated tlv encryption")
# always make sure that the new session has a new guid if it's not already known
guid = session.session_guid
if guid == "\x00" * 16
guid = [SecureRandom.uuid.gsub(/-/, '')].pack('H*')
session.core.set_session_guid(guid)
session.session_guid = guid
# TODO: New stageless session, do some account in the DB so we can track it later.
else
# TODO: This session was either staged or previously known, and so we shold do some accounting here!
end
# Call registered on_session callbacks
super
if datastore['AutoLoadStdapi']
session.load_stdapi
if datastore['AutoSystemInfo']
session.load_session_info
end
# only load priv on native windows
if session.platform == 'windows' && [ARCH_X86, ARCH_X64].include?(session.arch)
session.load_priv rescue nil
end
end
if session.platform == 'android'
session.load_android
end
[ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key|
unless datastore[key].empty?
args = Shellwords.shellwords( datastore[key] )
print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")
session.execute_script(args.shift, *args)
end
end
end
# Defer the session initialization to the Session Manager scheduler
framework.sessions.schedule init_session
end
end
end
end

View File

@ -205,6 +205,8 @@ protected
if self.session.respond_to?('create_session')
s = self.session.create_session(conn, opts)
else
STDERR.puts("create_session caller: #{caller.inspect}\n")
STDERR.puts("create_session opts: #{opts.inspect}\n")
s = self.session.new(conn, opts)
end
rescue ::Exception => e
@ -240,16 +242,15 @@ protected
# new session.
#
def register_session(session)
STDERR.puts("Registering session\n")
# Register the session with the framework
framework.sessions.register(session)
STDERR.puts("Calling on_session\n")
# Call the handler's on_session() method
on_session(session)
# Process the auto-run scripts for this session
if session.respond_to?('process_autoruns')
session.process_autoruns(datastore)
end
session.bootstrap(datastore)
STDERR.puts("Called on_session\n")
# If there is an exploit associated with this payload, then let's notify
# anyone who is interested that this exploit succeeded

View File

@ -89,6 +89,7 @@ module Msf::Payload::Stager
#
# @return [String,nil]
def stage_payload(opts = {})
STDERR.puts("In stager stage_payload: #{opts.inspect}\n")
if module_info['Stage']
return module_info['Stage']['Payload']
end
@ -165,8 +166,6 @@ module Msf::Payload::Stager
# If the stage should be sent over the client connection that is
# established (which is the default), then go ahead and transmit it.
if (stage_over_connection?)
opts = {}
if respond_to? :include_send_uuid
if include_send_uuid
uuid_raw = conn.get_once(16, 1)

View File

@ -73,8 +73,8 @@ module Msf::Payload::TransportConfig
ds = opts[:datastore] || datastore
{
scheme: 'pipe',
lhost: ds['PIPEHOST'],
uri: "/#{ds['PIPENAME']}"
lhost: ds[:pipe_host] || ds['PIPEHOST'],
uri: "/#{ds[:pipe_host] || ds['PIPENAME']}"
}.merge(timeout_config(opts))
end
@ -83,9 +83,9 @@ private
def timeout_config(opts={})
ds = opts[:datastore] || datastore
{
comm_timeout: ds['SessionCommunicationTimeout'].to_i,
retry_total: ds['SessionRetryTotal'].to_i,
retry_wait: ds['SessionRetryWait'].to_i
comm_timeout: (ds[:comm_timeout] || ds['SessionCommunicationTimeout']).to_i,
retry_total: (ds[:retry_total] || ds['SessionRetryTotal']).to_i,
retry_wait: (ds[:retry_wait] || ds['SessionRetryWait']).to_i
}
end

View File

@ -68,6 +68,8 @@ module Payload::Windows::MeterpreterLoader
end
def stage_payload(opts={})
STDERR.puts("In stage_payload: #{opts.inspect}\n")
stage_meterpreter(opts) + generate_config(opts)
end
@ -77,13 +79,14 @@ module Payload::Windows::MeterpreterLoader
# create the configuration block, which for staged connections is really simple.
config_opts = {
arch: opts[:uuid].arch,
exitfunk: ds['EXITFUNC'],
expiration: ds['SessionExpirationTimeout'].to_i,
uuid: opts[:uuid],
transports: opts[:transport_config] || [transport_config(opts)],
extensions: [],
stageless: opts[:stageless] == true
arch: opts[:uuid].arch,
null_session_guid: opts[:null_session_guid] == true,
exitfunk: ds[:exit_func] || ds['EXITFUNC'],
expiration: (ds[:expiration] || ds['SessionExpirationTimeout']).to_i,
uuid: opts[:uuid],
transports: opts[:transport_config] || [transport_config(opts)],
extensions: [],
stageless: opts[:stageless] == true
}
# create the configuration instance based off the parameters

View File

@ -53,12 +53,14 @@ private
# if no session guid is given then we'll just pass the blank
# guid through. this is important for stageless payloads
if opts[:stageless] == true
if opts[:stageless] == true || opts[:null_session_guid] == true
session_guid = "\x00" * 16
else
session_guid = [SecureRandom.uuid.gsub(/-/, '')].pack('H*')
end
STDERR.puts("**** Session config expiration: #{opts[:expiration]}\n")
session_data = [
0, # comms socket, patched in by the stager
exit_func, # exit function identifer

View File

@ -81,6 +81,7 @@ class Client
# which communication with the server will be performed.
#
def initialize(sock, opts={})
STDERR.puts("init in client\n")
init_meterpreter(sock, opts)
end
@ -107,6 +108,7 @@ class Client
# Initializes the meterpreter client instance
#
def init_meterpreter(sock,opts={})
STDERR.puts("init_meterpreter in client.rb\n")
self.sock = sock
self.parser = PacketParser.new
self.ext = ObjectAliases.new
@ -120,12 +122,25 @@ class Client
self.conn_id = opts[:conn_id]
self.url = opts[:url]
self.ssl = opts[:ssl]
self.expiration = opts[:expiration]
self.comm_timeout = opts[:comm_timeout]
self.retry_total = opts[:retry_total]
self.retry_wait = opts[:retry_wait]
self.passive_dispatcher = opts[:passive_dispatcher]
self.pivot_session = opts[:pivot_session]
if self.pivot_session
self.expiration = self.pivot_session.expiration
self.comm_timeout = self.pivot_session.comm_timeout
self.retry_total = self.pivot_session.retry_total
self.retry_wait = self.pivot_session.retry_wait
else
self.expiration = opts[:expiration]
self.comm_timeout = opts[:comm_timeout]
self.retry_total = opts[:retry_total]
self.retry_wait = opts[:retry_wait]
self.passive_dispatcher = opts[:passive_dispatcher]
end
STDERR.puts("Expr; #{self.expiration.inspect}\n")
STDERR.puts("Comm: #{self.comm_timeout.inspect}\n")
STDERR.puts("TOT: #{self.retry_total.inspect}\n")
STDERR.puts("Wait: #{self.retry_wait.inspect}\n")
self.response_timeout = opts[:timeout] || self.class.default_timeout
self.send_keepalives = true

View File

@ -734,21 +734,30 @@ class ClientCore < Extension
# Negotiates the use of encryption at the TLV level
#
def negotiate_tlv_encryption
STDERR.puts("negotiate_tlv_encryption entry: #{client.inspect}\n")
sym_key = nil
rsa_key = OpenSSL::PKey::RSA.new(2048)
rsa_pub_key = rsa_key.public_key
STDERR.puts("negotiate_tlv_encryption 1\n")
request = Packet.create_request('core_negotiate_tlv_encryption')
STDERR.puts("negotiate_tlv_encryption 2\n")
request.add_tlv(TLV_TYPE_RSA_PUB_KEY, rsa_pub_key.to_pem)
STDERR.puts("negotiate_tlv_encryption 3\n")
begin
response = client.send_request(request)
STDERR.puts("negotiate_tlv_encryption 4\n")
key_enc = response.get_tlv_value(TLV_TYPE_ENC_SYM_KEY)
STDERR.puts("negotiate_tlv_encryption 5\n")
key_type = response.get_tlv_value(TLV_TYPE_SYM_KEY_TYPE)
STDERR.puts("negotiate_tlv_encryption 6\n")
if key_enc
STDERR.puts("negotiate_tlv_encryption 7\n")
sym_key = rsa_key.private_decrypt(key_enc, OpenSSL::PKey::RSA::PKCS1_PADDING)
else
STDERR.puts("negotiate_tlv_encryption 8\n")
sym_key = response.get_tlv_value(TLV_TYPE_SYM_KEY)
end
rescue OpenSSL::PKey::RSAError, Rex::Post::Meterpreter::RequestError

View File

@ -831,9 +831,7 @@ class Packet < GroupTlv
def parse_header!
xor_key = self.raw.unpack('A4')[0]
data = xor_bytes(xor_key, self.raw[0..PACKET_HEADER_SIZE])
STDERR.puts("extracting header values\n")
_, self.session_guid, self.encrypt_flags, self.length, self.type = data.unpack('a4a16NNN')
STDERR.puts("extracted header values\n")
end
#

View File

@ -161,6 +161,7 @@ module PacketDispatcher
if self.pivot_session
opts[:session_guid] = self.session_guid
opts[:tlv_enc_key] = self.tlv_enc_key
STDERR.puts("Pivot session, setting guid: #{self.session_guid.unpack('H*')[0]}...\n")
return self.pivot_session.send_packet(packet, opts)
end
@ -168,13 +169,25 @@ module PacketDispatcher
add_response_waiter(packet, opts[:completion_routine], opts[:completion_param])
end
session_guid = opts[:session_guid] || self.session_guid
tlv_enc_key = opts[:tlv_enc_key] || self.tlv_enc_key
session_guid = self.session_guid
tlv_enc_key = self.tlv_enc_key
# if a session guid is provided, use all the details provided
if opts[:session_guid]
session_guid = opts[:session_guid]
tlv_enc_key = opts[:tlv_enc_key]
end
STDERR.puts(" Current Session GUID: #{self.session_guid.unpack('H*')[0]}\n")
STDERR.puts("Provided Session GUID: #{session_guid.unpack('H*')[0]}\n")
STDERR.puts("Using tlv_enc_key: #{tlv_enc_key.inspect}\n")
bytes = 0
raw = packet.to_r(session_guid, tlv_enc_key)
err = nil
STDERR.puts("Outgoing packet is #{raw.length} bytes\n")
# Short-circuit send when using a passive dispatcher
if self.passive_service
send_queue.push(raw)
@ -236,37 +249,45 @@ module PacketDispatcher
# @param timeout [Integer,nil] number of seconds to wait, or nil to wait
# forever
def send_packet_wait_response(packet, timeout)
STDERR.puts("send_packet_wait_response entry\n")
# First, add the waiter association for the supplied packet
waiter = add_response_waiter(packet)
STDERR.puts("send_packet_wait_response 1\n")
bytes_written = send_packet(packet)
STDERR.puts("send_packet_wait_response 2\n")
# Transmit the packet
if (bytes_written.to_i <= 0)
STDERR.puts("send_packet_wait_response 3\n")
# Remove the waiter if we failed to send the packet.
remove_response_waiter(waiter)
return nil
end
STDERR.puts("send_packet_wait_response 4\n")
if not timeout
return nil
end
STDERR.puts("send_packet_wait_response 5 #{waiter.inspect}\n")
# Wait for the supplied time interval
STDERR.puts("Waiting for the response: #{waiter.inspect}\n")
response = waiter.wait(timeout)
STDERR.puts("Response found\n")
STDERR.puts("send_packet_wait_response 6\n")
# Remove the waiter from the list of waiters in case it wasn't
# removed. This happens if the waiter timed out above.
remove_response_waiter(waiter)
STDERR.puts("send_packet_wait_response 7\n")
# wire in the UUID for this, as it should be part of every response
# packet
if response && !self.payload_uuid
STDERR.puts("send_packet_wait_response 8\n")
uuid = response.get_tlv_value(TLV_TYPE_UUID)
self.payload_uuid = Msf::Payload::UUID.new({:raw => uuid}) if uuid
end
STDERR.puts("send_packet_wait_response 9\n")
# Return the response packet, if any
return response
@ -438,9 +459,7 @@ module PacketDispatcher
def receive_packet
packet = parser.recv(self.sock)
if packet
STDERR.puts("here!\n")
packet.parse_header!
STDERR.puts("Packet: #{packet.inspect}\n")
if self.session_guid == "\x00" * 16
self.session_guid = packet.session_guid.dup
end
@ -474,9 +493,11 @@ module PacketDispatcher
#
def add_response_waiter(request, completion_routine = nil, completion_param = nil)
if self.pivot_session
STDERR.puts("Adding waiter in pivoted session: #{self.pivot_session}\n")
return self.pivot_session.add_response_waiter(request, completion_routine, completion_param)
end
STDERR.puts("Adding waiting in current session: #{self.inspect}\n")
waiter = PacketResponseWaiter.new(request.rid, completion_routine, completion_param)
self.waiters << waiter
@ -538,17 +559,25 @@ module PacketDispatcher
def dispatch_inbound_packet(packet)
handled = false
STDERR.puts("Inbound packet: #{packet.session_guid.unpack('H*')[0]}\n")
pivot = self.find_pivot(packet.session_guid)
STDERR.puts("Pivot is: #{pivot.inspect}\n")
pivot.dispatch_inbound_packet(packet) if pivot
tlv_enc_key = self.tlv_enc_key
STDERR.puts("Getting enc key\n")
tlv_enc_key = pivot.pivoted_session.tlv_enc_key if pivot
STDERR.puts("Got enc key #{tlv_enc_key.inspect}\n")
packet.from_r(self.tlv_enc_key)
packet.from_r(tlv_enc_key)
STDERR.puts("Decrypted packet: #{packet.inspect}\n")
# Update our last reply time
self.last_checkin = Time.now
pivot.pivoted_session.last_checkin = self.last_checkin if pivot
# If the packet is a response, try to notify any potential
# waiters
STDERR.puts("Notifying response waiter for packet")
if packet.response? && notify_response_waiter(packet)
return true
end

View File

@ -10,11 +10,11 @@ module Meterpreter
class PivotListener
attr_accessor :id
attr_accessor :stager
attr_accessor :session_class
def initialize(stager)
def initialize(session_class)
self.id = [SecureRandom.uuid.gsub(/-/, '')].pack('H*')
self.stager = stager
self.session_class = session_class
end
end
@ -48,25 +48,49 @@ class Pivot
end
end
def Pivot.create_listener(client, opts={})
def Pivot.create_named_pipe_listener(client, opts={})
STDERR.puts("Create listener: #{opts}\n")
request = Packet.create_request('core_pivot_add')
request.add_tlv(TLV_TYPE_PIVOT_NAMED_PIPE_NAME, opts[:pipe_name])
# TODO: use the framework to generate the whole lot, including a session type
c = Class.new(::Msf::Payload)
c.include(::Msf::Payload::Stager)
#c.include(::Msf::Payload::TransportConfig)
c.include(::Msf::Payload::TransportConfig)
# Include the appropriate reflective dll injection module for the target process architecture...
if opts[:arch] == ARCH_X86
c.include(::Msf::Payload::Windows::MeterpreterLoader)
elsif opts[:arch] == ARCH_X64
c.include(::Msf::Payload::Windows::MeterpreterLoader_x64)
# TODO: add more platforms
case opts[:platform]
when 'windows'
# Include the appropriate reflective dll injection module for the target process architecture...
if opts[:arch] == ARCH_X86
STDERR.puts("Including Meterp Loader x86\n")
c.include(::Msf::Payload::Windows::MeterpreterLoader)
elsif opts[:arch] == ARCH_X64
STDERR.puts("Including Meterp Loader x64\n")
c.include(::Msf::Payload::Windows::MeterpreterLoader_x64)
else
STDERR.puts("Not including a loader for #{opts[:arch]}\n")
end
end
# TODO: should we be generating configuration inside Meterpreter like
# we do for migration, or just having a fixed one like we are now?
#uuid = Msf::Payload::UUID.new({
# arch: opts[:arch],
# platform: opts[:platform]
#})
stage_opts = {
#uuid: uuid,
arch: opts[:arch],
force_write_handle: true,
null_session_guid: true,
datastore: {
exit_func: opts[:exit_func] || 'process',
expiration: client.expiration,
comm_timeout: client.comm_timeout,
retry_total: client.retry_total,
retry_wait: client.retry_wait,
'PIPEHOST' => opts[:pipe_host],
'PIPENAME' => opts[:pipe_name]
}
@ -76,9 +100,11 @@ class Pivot
stager = c.new()
stage_opts[:transport_config] = [stager.transport_config_reverse_named_pipe(stage_opts)]
#STDERR.puts("Stager: #{stager.inspect}\n")
stage = stager.stage_payload(stage_opts)
#STDERR.puts("Stage: #{stage.inspect}\n")
pivot_listener = PivotListener.new(stager)
pivot_listener = PivotListener.new(::Msf::Sessions::Meterpreter_x86_Win)
request.add_tlv(TLV_TYPE_PIVOT_STAGE_DATA, stage)
request.add_tlv(TLV_TYPE_PIVOT_STAGE_DATA_SIZE, stage.length)
@ -99,15 +125,8 @@ class Pivot
}
listener = client.find_pivot_listener(listener_id)
self.pivoted_session = listener.session_class.new(nil, opts)
STDERR.puts("about to create the pivoted session instance 3\n")
begin
STDERR.puts("Stage: #{listener.stager.inspect}\n")
STDERR.puts("Stage Session: #{listener.stager.session.inspect}\n")
self.pivoted_session = listener.stager.session.new(nil, opts)
rescue => e
STDERR.puts(e.inspect)
end
STDERR.puts("pivoted session instance created: #{self.pivoted_session.inspect}\n")
self.client.add_pivot(self)
@ -115,7 +134,7 @@ class Pivot
STDERR.puts("Setting the framework instance\n")
self.pivoted_session.framework = self.client.framework
STDERR.puts("Invoking the on_session method\n")
self.pivoted_session.on_session(self.pivoted_session)
self.pivoted_session.bootstrap({'AutoVerifySessionTimeout' => 30})
STDERR.puts("Registering the session with the framework\n")
self.client.framework.sessions.register(self.pivoted_session)
STDERR.puts("done!\n")