diff --git a/documentation/TODO b/documentation/TODO index a3f53b746b..ca2bb62a8f 100644 --- a/documentation/TODO +++ b/documentation/TODO @@ -1,86 +1,5 @@ -X - evasion class -X - set_level(evlvl) -X - high? -X - medium? -- testing framework - - run all the exploits through all the diff payload handler permutations - - simulate clients for each different permutation -X - seh exploit mixin -X - generate padded registration records -X - move jump around -X - use multi-size jump -- return address pool - - exploits say what modules they have present - - target says what platform is being exploited - - target says what type of instruction is viable - - pool returns a random return address for that target - - automatic opcode db synchronization -- add module meta-info - - payloads - - calling convention (staged shell is incompat with ord stagers) - - stack requirements - - etc -- exploit reloading -- payload convention - - make it so stages/stagers are queried for compatibility - - make it so exploits query convention compat - - ws2ord stuff -- make it possible to share handlers? - - payloads that use the same handlers could be shared +This file contains things that need to be done that aren't in the plan: -X - switch to x86 from ia32 -X - exploit kick-off -X - payload generation -X - generate payload for target -X - encoder payload for target -X - loop encoders on failure -X - pad nops -X - handler init -X - setup handler -X - start handler -X - exploit -X - call exploit -X ... wait for session ... -X - handler cleanup -X - stop handler -X - cleanup handler -X - -X -X - add the concept of services to framework: -X - instead, just make it a singleton, doesn't belong on framework -X - add port forward service -X -X# first parameter is class that must inherit from Rex::Proto so that it has .alias -Xservice = framework.services.start(Rex::Proto::HTTP::Server, 'Port' => 80, 'Host' => '127.0.0.1') -Xservice = framework.services['HTTP Server'] -X -Xoverrides any existing resource handler with this name: -Xservice.create_resource("/uri", Proc.new { |conn, request| -X}) -X -Xservice.remove_resource("/uri") -Xservice.shutdown -X ^- reference counted, only terminates when reference count drops to zero -X -X- exploit mixins -X - Http -X - Http::Client -X connect -X create_request -X send_request -X handler -X - Http::Server -X handle_request(req) -X create_response -X send_response -X- findsock payloads -X - findsock handler -- meterpreter -X - more ui wrapping -X - fix route addition/removal in stdapi server dll (mib structure issue) -X - fix interactive stream pool channels -X - make migrate on server not open with PROCESS_ALL_ACCESS -N - dupe input instance when passing to sessions -X - fix module loading order -X - problems with dllinject getting loaded after meterpreter due to dependencies -X - fix default handle inheritance in meterp process execution +- revisit pivoting + - connections seemed slow + - data transfers seemed slow diff --git a/documentation/plan.txt b/documentation/plan.txt index 466c504974..c03c108910 100644 --- a/documentation/plan.txt +++ b/documentation/plan.txt @@ -1,9 +1,10 @@ The following things are required for the December alpha release: - rex - - post-exploitation - - meterpreter - - pivoting +X - post-exploitation +X - meterpreter +X - pivoting +X - portfwd command - networking - switch board routing table for pivoting - meterpreter 'comm' support diff --git a/lib/msf/core/exploit/tcp.rb b/lib/msf/core/exploit/tcp.rb index fcf4cdade7..0d5574d7b6 100644 --- a/lib/msf/core/exploit/tcp.rb +++ b/lib/msf/core/exploit/tcp.rb @@ -165,7 +165,9 @@ module Exploit::Remote::TcpServer # def stop_service if (service) - self.service.stop + Rex::ServiceManager.stop_service(self.service) + + self.service.deref self.service = nil end end diff --git a/lib/rex/constants.rb b/lib/rex/constants.rb index bd001135b4..3822e1c89f 100644 --- a/lib/rex/constants.rb +++ b/lib/rex/constants.rb @@ -16,3 +16,5 @@ LEV_0 = 0 LEV_1 = 1 LEV_2 = 2 LEV_3 = 3 + + diff --git a/lib/rex/exceptions.rb b/lib/rex/exceptions.rb index f59cdbb167..5fd7bc9e37 100644 --- a/lib/rex/exceptions.rb +++ b/lib/rex/exceptions.rb @@ -65,6 +65,25 @@ class AmbiguousArgumentError < ::RuntimeError end end +# +# This error is thrown when a stream is detected as being closed. +# +class StreamClosedError < ::IOError + include Exception + + def initialize(stream) + @stream = stream + end + + def stream + @stream + end + + def to_s + "Stream #{@stream} is closed." + end +end + ##### ##### ## diff --git a/lib/rex/exceptions.rb.ut.rb b/lib/rex/exceptions.rb.ut.rb index 47ffbf024c..a9c2d9b3a2 100644 --- a/lib/rex/exceptions.rb.ut.rb +++ b/lib/rex/exceptions.rb.ut.rb @@ -21,6 +21,7 @@ class UnitTest < Test::Unit::TestCase begin raise mod.new + rescue ::ArgumentError rescue mod => detail assert_respond_to(detail, 'to_s', "#{mod} does not implement to_s") assert_not_nil(detail.to_s, "invalid to_s") diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/socket.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/socket.rb index a79664ee3e..74d877ffff 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/socket.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/socket.rb @@ -76,7 +76,7 @@ class Socket # representation of the left side of the socket for # the caller to use if (channel != nil) - res = Rex::Socket::Stream.new(channel.lsock) + res = channel.lsock end elsif (params.udp?) if (params.server?) @@ -126,7 +126,25 @@ protected while (1) # Watch for data - socks = select(monitored_sockets, nil, nil, 1) + begin + socks = select(monitored_sockets, nil, nil, 1) + rescue StreamClosedError => e + channel = monitored_socket_channels[e.stream.object_id] + + dlog("monitor_channels: channel #{channel} closed (#{e.stream})", + 'rex', LEV_3) + + if (channel) + begin + channel.close + rescue IOError + end + + remove_monitored_socket(e.stream) + end + + next + end # No data? if (socks == nil || socks[0] == nil) diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb index 952fec2a60..9cb3849778 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb @@ -1,4 +1,5 @@ require 'rex/post/meterpreter' +require 'rex/service_manager' module Rex module Post @@ -20,11 +21,21 @@ class Console::CommandDispatcher::Stdapi::Net include Console::CommandDispatcher # - # Options for the generate command + # Options for the route command # @@route_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ]) + # + # Options for the portfwd command + # + @@portfwd_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner." ], + "-l" => [ true, "The local port to listen on." ], + "-r" => [ true, "The remote host to connect to." ], + "-p" => [ true, "The remote port to connect to." ], + "-L" => [ true, "The local host to listen on (optional)." ]) + # # List of supported commands # @@ -32,6 +43,7 @@ class Console::CommandDispatcher::Stdapi::Net { "ipconfig" => "Display interfaces", "route" => "View and modify the routing table", + "portfwd" => "Forward a local port to a remote service", } end @@ -118,6 +130,132 @@ class Console::CommandDispatcher::Stdapi::Net end end + # + # Starts and stops local port forwards to remote hosts on the target + # network. This provides an elementary pivoting interface. + # + def cmd_portfwd(*args) + args.unshift("list") unless args.length + + # For clarity's sake. + lport = nil + lhost = nil + rport = nil + rhost = nil + + # Parse the options + @@portfwd_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print( + "Usage: route [-h] [add / delete / list] [args]\n\n" + + @@portfwd_opts.usage) + return true + when "-l" + lport = val.to_i + when "-L" + lhost = val + when "-p" + rport = val.to_i + when "-r" + rhost = val + end + } + + # Process the command + case args.shift + when "list" + + # Get the service context + service = Rex::ServiceManager.start(Rex::Services::LocalRelay) + + begin + + cnt = 0 + + # Enumerate each TCP relay + service.each_tcp_relay { |lhost, lport, rhost, rport, opts| + next if (opts['MeterpreterRelay'] == nil) + + print_line("#{cnt}: #{lhost}:#{lport} -> #{rhost}:#{rport}") + + cnt += 1 + } + + print_line + print_line("#{cnt} total local port forwards.") + + ensure + service.deref + end + + when "add" + + # Validate parameters + if (!lport or !rhost or !rport) + print_error("You must supply a local port, remote host, and remote port.") + return + end + + # Build a local port forward in association with the channel + service = Rex::ServiceManager.start(Rex::Services::LocalRelay) + + begin + # Start the local TCP relay in association with this stream + service.start_tcp_relay(lport, + 'LocalHost' => lhost, + 'PeerHost' => rhost, + 'PeerPort' => rport, + 'MeterpreterRelay' => true, + 'OnLocalConnection' => Proc.new { |relay, lfd| + create_tcp_channel(relay) + }) + + print_status("Local TCP relay created: #{lhost || '0.0.0.0'}:#{lport} <-> #{rhost}:#{rport}") + + ensure + # Lost our reference to the service now that we're done with it + service.deref + end + + # Delete local port forwards + when "delete" + + # No local port, no love. + if (!lport) + print_error("You must supply a local port.") + return + end + + service = Rex::ServiceManager.start(Rex::Services::LocalRelay) + + # Stop the service + begin + if (service.stop_tcp_relay(lport, lhost)) + print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}") + else + print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}") + end + ensure + service.deref + end + + end + end + +protected + + # + # Creates a TCP channel using the supplied relay context. + # + def create_tcp_channel(relay) + client.net.socket.create( + Rex::Socket::Parameters.new( + 'PeerHost' => relay.opts['PeerHost'], + 'PeerPort' => relay.opts['PeerPort'], + 'Proto' => 'tcp')) + end + end end diff --git a/lib/rex/service.rb b/lib/rex/service.rb index 3bd834aed9..1e181ebc64 100644 --- a/lib/rex/service.rb +++ b/lib/rex/service.rb @@ -16,12 +16,17 @@ module Rex module Service include Ref + require 'rex/services/local_relay' + # # Calls stop on the service once the ref count drops. # def cleanup stop end + + attr_accessor :alias + end end diff --git a/lib/rex/service_manager.rb b/lib/rex/service_manager.rb index dbe16d32cf..57c482d948 100644 --- a/lib/rex/service_manager.rb +++ b/lib/rex/service_manager.rb @@ -30,8 +30,16 @@ class ServiceManager < Hash # # Calls the instance method to stop a service. # - def self.stop(als) - self.instance.stop(als) + def self.stop(klass, *args) + self.instance.stop(klass, *args) + end + + def self.stop_by_alias(als) + self.instance.stop_by_alias(als) + end + + def self.stop_service(service) + self.instance.stop_service(service) end # @@ -39,7 +47,7 @@ class ServiceManager < Hash # def start(klass, *args) # Get the hardcore alias. - hals = "__#{klass.name}#{args.to_s}" + hals = hardcore_alias(klass, *args) # Has a service already been constructed for this guy? If so, increment # its reference count like it aint no thang. @@ -70,22 +78,59 @@ class ServiceManager < Hash # Alias associate and initialize reference counting self[als] = self[hals] = inst.refinit + + # Pass the caller a reference + inst.ref + + inst + end + + # + # Stop a service using a given klass and arguments. These should mirror + # what was originally passed to start exactly. If the reference count of + # the service drops to zero the service will be destroyed. + # + def stop(klass, *args) + stop_service(hals[hardcore_alias(klass, *args)]) end # # Stops a service using the provided alias # - def stop(als) + def stop_by_alias(als) + stop_service(self[als]) + end + + # + # Stops a service instance. + # + def stop_service(inst) # Stop the service and be done wif it, but only if the number of # references has dropped to zero - if ((inst = self[als]) and - (inst.deref)) + if (inst) # Since the instance may have multiple aliases, scan through # all the pairs for matching stuff. self.each_pair { |cals, cinst| self.delete(cals) if (inst == cinst) } + + # Lose the list-held reference to the instance + inst.deref + + return true end + + # Return false if the service isn't there + return false + end + +protected + + # + # Returns the alias for a given service instance. + # + def hardcore_alias(klass, *args) + "__#{klass.name}#{args.to_s}" end end diff --git a/lib/rex/service_manager.rb.ut.rb b/lib/rex/service_manager.rb.ut.rb index c2938a005b..75d12b0f08 100644 --- a/lib/rex/service_manager.rb.ut.rb +++ b/lib/rex/service_manager.rb.ut.rb @@ -22,9 +22,9 @@ class Rex::ServiceManager::UnitTest < Test::Unit::TestCase assert_equal("HTTP Server", s.alias) assert_equal("HTTP Server 1", z.alias) ensure - c.stop(s.alias) if (s) - c.stop(z.alias) if (z) - c.stop(t.alias) if (t) + c.stop_by_alias(s.alias) if (s) + c.stop_by_alias(z.alias) if (z) + c.stop_by_alias(t.alias) if (t) end end diff --git a/lib/rex/services/local_relay.rb b/lib/rex/services/local_relay.rb new file mode 100644 index 0000000000..507c5aeb27 --- /dev/null +++ b/lib/rex/services/local_relay.rb @@ -0,0 +1,404 @@ +require 'thread' +require 'rex/socket' + +module Rex +module Services + +### +# +# LocalRelay +# ---------- +# +# This service acts as a local TCP relay whereby clients can connect to a +# local listener that forwards to an arbitrary remote endpoint. Interaction +# with the remote endpoint socket requires that it implement the +# Rex::IO::Stream interface. +# +### +class LocalRelay + + include Rex::Service + + ### + # + # Stream + # ------ + # + # This module is used to extend streams such that they can be associated + # with a relay context and the other side of the stream. + # + ### + module Stream + + # + # This method is called when the other side has data that has been read + # in. + # + def on_other_data(data) + if (relay.on_other_data_proc) + relay.on_other_data_proc.call(relay, self, data) + # By default, simply push all the data to our side. + else + put(data) + end + end + + attr_accessor :relay + attr_accessor :other_stream + end + + ### + # + # StreamServer + # ------------ + # + # This module is used to extend stream servers such that they can be + # associated with a relay context. + # + ### + module StreamServer + + # + # This method is called when the stream server receives a local + # connection such that the remote half can be allocated. The return + # value of the callback should be a Stream instance. + # + def on_local_connection(relay, lfd) + if (relay.on_local_connection_proc) + relay.on_local_connection_proc.call(relay, lfd) + end + end + + attr_accessor :relay + end + + + ### + # + # Relay + # ----- + # + # This class acts as an instance of a given local relay. + # + ### + class Relay + + def initialize(name, listener, opts = {}) + self.name = name + self.listener = listener + self.opts = opts + self.on_local_connection_proc = opts['OnLocalConnection'] + self.on_other_data_proc = opts['OnOtherData'] + end + + def shutdown + listener.shutdown if (listener) + end + + def close + listener.close if (listener) + listener = nil + end + + attr_reader :name, :listener, :opts + attr_accessor :on_local_connection_proc + attr_accessor :on_other_data_proc + protected + attr_writer :name, :listener, :opts + + end + + # + # Initializes the local tcp relay monitor. + # + def initialize + self.relays = Hash.new + self.rfds = Array.new + self.relay_thread = nil + self.relay_mutex = Mutex.new + end + + ## + # + # Service interface implementors + # + ## + + # + # Returns the alias for this service. + # + def alias + super || "Local Relay" + end + + # + # Starts the thread that monitors the local relays. + # + def start + self.relay_thread = Thread.new { + begin + monitor_relays + rescue + elog("Error in #{self} monitor_relays: #{$!}", 'rex') + end + } if (!self.relay_thread) + end + + # + # Stops the thread that monitors the local relays and destroys all local + # listeners. + # + def stop + if (self.relay_thread) + self.relay_thread.kill + self.relay_thread = nil + end + + self.relay_mutex.synchronize { + self.relays.delete_if { |k, v| + v.shutdown + v.close + true + } + } + + # Flush the relay list and read fd list + self.relays.clear + self.rfds.clear + end + + ## + # + # Adding/removing local tcp relays + # + ## + + # + # Starts a local TCP relay. + # + def start_tcp_relay(lport, opts = {}) + # Make sure our options are valid + if (opts['PeerHost'] == nil or opts['PeerPort'] == nil) + raise ArgumentError, "Missing peer host or peer port.", caller + end + + listener = Rex::Socket.create_tcp_server( + 'LocalHost' => opts['LocalHost'], + 'LocalPort' => lport) + + opts['LocalPort'] = lport + opts['__RelayType'] = 'tcp' + + start_relay(listener, lport.to_s + (opts['LocalHost'] || '0.0.0.0'), opts) + end + + # + # Starts a local relay on the supplied local port. This listener will call + # the supplied callback procedures when various events occur. + # + def start_relay(stream_server, name, opts = {}) + # Create a Relay instance with the local stream and remote stream + relay = Relay.new(name, stream_server, opts) + + # Extend the stream_server so that we can associate it with this relay + stream_server.extend(StreamServer) + stream_server.relay = relay + + # Add the stream associations the appropriate lists and hashes + self.relay_mutex.synchronize { + self.relays[name] = relay + + self.rfds << stream_server + } + end + + # + # Stops relaying on a given local port. + # + def stop_tcp_relay(lport, lhost = nil) + stop_relay(lport.to_s + (lhost || '0.0.0.0')) + end + + # + # Stops a relay with a given name. + # + def stop_relay(name) + rv = false + + self.relay_mutex.synchronize { + relay = self.relays[name] + + if (relay) + close_relay(relay) + rv = true + end + } + + rv + end + + # + # Enumerate each TCP relay + # + def each_tcp_relay(&block) + self.relays.each_pair { |name, relay| + next if (relay.opts['__RelayType'] != 'tcp') + + yield( + relay.opts['LocalHost'] || '0.0.0.0', + relay.opts['LocalPort'], + relay.opts['PeerHost'], + relay.opts['PeerPort'], + relay.opts) + } + end + +protected + + attr_accessor :relays, :relay_thread, :relay_mutex + attr_accessor :rfds + + # + # Closes an cleans up a specific relay + # + def close_relay(relay) + self.rfds.delete(relay.listener) + self.relays.delete(relay.name) + + relay.shutdown + relay.close + end + + # + # Closes a specific relay connection without tearing down the actual relay + # itself. + # + def close_relay_conn(fd) + relay = fd.relay + ofd = fd.other_stream + + self.rfds.delete(fd) + + begin + fd.shutdown + fd.close + rescue IOError + end + + if (ofd) + self.rfds.delete(ofd) + + begin + ofd.shutdown + ofd.close + rescue IOError + end + end + end + + # + # Accepts a client connection on a local relay. + # + def accept_relay_conn(srvfd) + relay = srvfd.relay + + begin + dlog("Accepting relay client connection...", 'rex', LEV_3) + + # Accept the child connection + lfd = srvfd.accept + dlog("Got left side of relay: #{lfd}", 'rex', LEV_3) + + # Call the relay's on_local_connection method which should return a + # remote connection on success + rfd = srvfd.on_local_connection(relay, lfd) + dlog("Got right side of relay: #{lfd}", 'rex', LEV_3) + rescue + wlog("Failed to get remote half of local connection on relay #{relay.name}: #{$!}", 'rex') + end + + # If we have both sides, then we rock. Extend the instances, associate + # them with the relay, associate them with each other, and add them to + # the list of polling file descriptors + if (lfd and rfd) + lfd.extend(Stream) + rfd.extend(Stream) + + lfd.relay = relay + rfd.relay = relay + + lfd.other_stream = rfd + rfd.other_stream = lfd + + self.rfds << lfd + self.rfds << rfd + # Otherwise, we don't have both sides, we'll close them. + else + close_relay_conn(lfd) + end + end + + # + # Monitors the relays for data and passes it in both directions. + # + def monitor_relays + begin + + # Poll all the streams... + begin + socks = select(rfds, nil, nil, 0.2) + rescue StreamClosedError => e + dlog("monitor_relays: closing stream #{e.stream}", 'rex', LEV_3) + + # Close the relay connection that is associated with the stream + # closed error + if (e.stream.kind_of?(Stream)) + close_relay_conn(e.stream) + end + + dlog("monitor_relays: closed stream #{e.stream}", 'rex', LEV_3) + + next + rescue + elog("Error in #{self} monitor_relays select: #{$!}", 'rex') + return + end + + # If socks is nil, go again. + next unless socks + + # Process read-ready file descriptors, if any. + socks[0].each { |rfd| + + # If this file descriptor is a server, accept the connection + if (rfd.kind_of?(StreamServer)) + accept_relay_conn(rfd) + # Otherwise, it's a relay connection, read data from one side + # and write it to the other + else + begin + # Read from the read fd + data = rfd.sysread(16384) + + dlog("monitor_relays: sending #{data.length} bytes from #{rfd} to #{rfd.other_stream}", + 'rex', LEV_3) + + # Pass the data onto the other fd, most likely writing it. + rfd.other_stream.on_other_data(data) + # If we catch an EOFError, close the relay connection. + rescue EOFError + close_relay_conn(rfd) + rescue + elog("Error in #{self} monitor_relays read: #{$!}", 'rex') + end + end + + } if (socks[0]) + + end while true + end + +end + +end +end diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index d364d326cf..f2e781f826 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -140,3 +140,10 @@ protected end end + +# +# Globalized socket constants +# +SHUT_RDWR = ::Socket::SHUT_RDWR +SHUT_RD = ::Socket::SHUT_RD +SHUT_WR = ::Socket::SHUT_WR diff --git a/lib/rex/sync/thread_safe.rb b/lib/rex/sync/thread_safe.rb index 181b33dc20..3ca4d7c45e 100644 --- a/lib/rex/sync/thread_safe.rb +++ b/lib/rex/sync/thread_safe.rb @@ -26,8 +26,34 @@ module ThreadSafe left = t begin + orig_size = rfd.length if (rfd) + # Poll the set supplied to us at least once. - rv = ::IO.select(rfd, wfd, efd, DefaultCycle) + begin + rv = ::IO.select(rfd, wfd, efd, DefaultCycle) + rescue IOError + # If a stream was detected as being closed, re-raise the error as + # a StreamClosedError with the specific file descriptor that was + # detected as being closed. This is to better handle the case of + # a closed socket being detected so that it can be cleaned up and + # removed. + if (rfd) + rfd.each { |fd| + raise StreamClosedError.new(fd) if (fd.closed?) + } + end + + # If the original rfd length is not the same as the current + # length, then the list may have been altered and as such may not + # contain the socket that caused the IOError. This is a bad way + # to do this since it's possible that the array length could be + # back to the size that it was originally and yet have had the + # socket that caused the IOError to be removed. + return nil if (rfd and rfd.length != orig_size) + + # Re-raise the exception since we didn't handle it here. + raise $! + end return rv if (rv) diff --git a/msfconsole b/msfconsole index c0b5200bc4..367cc76f78 100755 --- a/msfconsole +++ b/msfconsole @@ -6,6 +6,9 @@ $:.unshift(File.join(File.dirname(__FILE__), '../lib')) require 'rex' require 'msf/ui' -register_log_source('core', Rex::Logging::Sinks::Flatfile.new('/tmp/msfcli.log')) +# TODO: streamline logging +f = Rex::Logging::Sinks::Flatfile.new('/tmp/msfcli.log') +register_log_source('rex', f) +register_log_source('core', f) Msf::Ui::Console::Driver.new.run