moved some code around for interactive channels, still not functional yet, boohoo
git-svn-id: file:///home/svn/incoming/trunk@2797 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
203f185ad7
commit
632a97823f
|
@ -48,3 +48,5 @@ service.shutdown
|
||||||
- findsock handler
|
- findsock handler
|
||||||
- meterpreter
|
- meterpreter
|
||||||
- more ui wrapping
|
- more ui wrapping
|
||||||
|
- fix route addition/removal in stdapi server dll (mib structure issue)
|
||||||
|
- fix interactive stream pool channels
|
||||||
|
|
|
@ -36,25 +36,7 @@ protected
|
||||||
# overriden by derived classes if they wish to do this another way.
|
# overriden by derived classes if they wish to do this another way.
|
||||||
#
|
#
|
||||||
def _interact
|
def _interact
|
||||||
while self.interacting
|
interact_stream(rstream)
|
||||||
# Select input and rstream
|
|
||||||
sd = Rex::ThreadSafe.select([ user_input.fd, rstream.fd ])
|
|
||||||
|
|
||||||
# Cycle through the items that have data
|
|
||||||
# From the rstream? Write to user_output.
|
|
||||||
sd[0].each { |s|
|
|
||||||
if (s == rstream.fd)
|
|
||||||
data = rstream.get
|
|
||||||
|
|
||||||
user_output.print(data)
|
|
||||||
# From user_input? Write to rstream.
|
|
||||||
elsif (s == user_input.fd)
|
|
||||||
data = user_input.gets
|
|
||||||
|
|
||||||
rstream.put(data)
|
|
||||||
end
|
|
||||||
} if (sd)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ module Interactive
|
||||||
# Interactive sessions by default may interact with the local user input
|
# Interactive sessions by default may interact with the local user input
|
||||||
# and output.
|
# and output.
|
||||||
#
|
#
|
||||||
include Rex::Ui::Subscriber
|
include Rex::Ui::Interactive
|
||||||
|
|
||||||
#
|
#
|
||||||
# Initialize's the session
|
# Initialize's the session
|
||||||
|
@ -55,75 +55,43 @@ module Interactive
|
||||||
rstream = nil
|
rstream = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Starts interacting with the session at the most raw level, simply
|
|
||||||
# forwarding input from user_input to rstream and forwarding input from
|
|
||||||
# rstream to user_output.
|
|
||||||
#
|
|
||||||
def interact
|
|
||||||
self.interacting = true
|
|
||||||
|
|
||||||
eof = false
|
|
||||||
|
|
||||||
# Handle suspend notifications
|
|
||||||
handle_suspend
|
|
||||||
|
|
||||||
callcc { |ctx|
|
|
||||||
# As long as we're interacting...
|
|
||||||
while (self.interacting == true)
|
|
||||||
begin
|
|
||||||
_interact
|
|
||||||
# If we get an interrupt exception, ask the user if they want to
|
|
||||||
# abort the interaction. If they do, then we return out of
|
|
||||||
# the interact function and call it a day.
|
|
||||||
rescue Interrupt
|
|
||||||
if (user_want_abort? == true)
|
|
||||||
eof = true
|
|
||||||
ctx.call
|
|
||||||
end
|
|
||||||
# If we reach EOF or the connection is reset...
|
|
||||||
rescue EOFError, Errno::ECONNRESET
|
|
||||||
dlog("Session #{name} got EOF, closing.", 'core', LEV_1)
|
|
||||||
eof = true
|
|
||||||
ctx.call
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
# Restore the suspend handler
|
|
||||||
restore_suspend
|
|
||||||
|
|
||||||
# If we hit end-of-file, then that means we should finish off this
|
|
||||||
# session and call it a day.
|
|
||||||
framework.sessions.deregister(self) if (eof == true)
|
|
||||||
|
|
||||||
# Return whether or not EOF was reached
|
|
||||||
return eof
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# The remote stream handle. Must inherit from Rex::IO::Stream.
|
# The remote stream handle. Must inherit from Rex::IO::Stream.
|
||||||
#
|
#
|
||||||
attr_accessor :rstream
|
attr_accessor :rstream
|
||||||
#
|
|
||||||
# Whether or not the session is currently being interacted with
|
|
||||||
#
|
|
||||||
attr_reader :interacting
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_writer :interacting
|
|
||||||
#
|
|
||||||
# The original suspend proc.
|
|
||||||
#
|
|
||||||
attr_accessor :orig_suspend
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Stub method that is meant to handler interaction
|
# Stub method that is meant to handler interaction
|
||||||
#
|
#
|
||||||
def _interact
|
def _interact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check to see if the user wants to abort
|
||||||
|
#
|
||||||
|
def _interrupt
|
||||||
|
user_want_abort?
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check to see if we should suspnd
|
||||||
|
#
|
||||||
|
def _suspend
|
||||||
|
# Ask the user if they would like to background the session
|
||||||
|
if (prompt_yesno("Background session #{name}?") == true)
|
||||||
|
self.interacting = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# If the session reaches EOF, deregister it.
|
||||||
|
#
|
||||||
|
def _interact_complete
|
||||||
|
framework.sessions.deregister(self)
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Checks to see if the user wants to abort
|
# Checks to see if the user wants to abort
|
||||||
#
|
#
|
||||||
|
@ -131,49 +99,6 @@ protected
|
||||||
prompt_yesno("Abort session #{name}?")
|
prompt_yesno("Abort session #{name}?")
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Installs a signal handler to monitor suspend signal notifications.
|
|
||||||
#
|
|
||||||
def handle_suspend
|
|
||||||
if (orig_suspend == nil)
|
|
||||||
self.orig_suspend = Signal.trap("TSTP") {
|
|
||||||
# Ask the user if they would like to background the session
|
|
||||||
if (prompt_yesno("Background session #{name}?") == true)
|
|
||||||
self.interacting = false
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Restores the previously installed signal handler for suspend
|
|
||||||
# notifications.
|
|
||||||
#
|
|
||||||
def restore_suspend
|
|
||||||
if (orig_suspend)
|
|
||||||
Signal.trap("TSTP", orig_suspend)
|
|
||||||
|
|
||||||
self.orig_suspend = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Prompt the user for input if possible.
|
|
||||||
#
|
|
||||||
def prompt(query)
|
|
||||||
if (user_output and user_input)
|
|
||||||
user_output.print("\n" + query)
|
|
||||||
user_input.gets
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Check the return value of a yes/no prompt
|
|
||||||
#
|
|
||||||
def prompt_yesno(query)
|
|
||||||
(prompt(query + " [y/N] ") =~ /^y/i) ? true : false
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -96,37 +96,7 @@ class Core
|
||||||
# Displays the command help banner
|
# Displays the command help banner
|
||||||
#
|
#
|
||||||
def cmd_help(*args)
|
def cmd_help(*args)
|
||||||
driver.dispatcher_stack.reverse.each { |dispatcher|
|
print(driver.help_to_s)
|
||||||
begin
|
|
||||||
commands = dispatcher.commands
|
|
||||||
rescue
|
|
||||||
commands = nil
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
# Display the commands
|
|
||||||
tbl = Table.new(
|
|
||||||
Table::Style::Default,
|
|
||||||
'Header' => "#{dispatcher.name} Commands",
|
|
||||||
'Columns' =>
|
|
||||||
[
|
|
||||||
'Command',
|
|
||||||
'Description'
|
|
||||||
],
|
|
||||||
'ColProps' =>
|
|
||||||
{
|
|
||||||
'Command' =>
|
|
||||||
{
|
|
||||||
'MaxWidth' => 12
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatcher.commands.sort.each { |c|
|
|
||||||
tbl << c
|
|
||||||
}
|
|
||||||
|
|
||||||
print(tbl.to_s)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -11,9 +11,6 @@ module Rex
|
||||||
#
|
#
|
||||||
###
|
###
|
||||||
module Exception
|
module Exception
|
||||||
def to_s
|
|
||||||
"An unknown exception occurred."
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class TimeoutError < Interrupt
|
class TimeoutError < Interrupt
|
||||||
|
@ -35,9 +32,9 @@ end
|
||||||
class RuntimeError < ::RuntimeError
|
class RuntimeError < ::RuntimeError
|
||||||
include Exception
|
include Exception
|
||||||
|
|
||||||
def to_s
|
# def to_s
|
||||||
"A runtime error occurred."
|
# "A runtime error occurred."
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ArgumentError < ::ArgumentError
|
class ArgumentError < ::ArgumentError
|
||||||
|
|
|
@ -33,24 +33,30 @@ module Stream
|
||||||
# Writes data to the stream.
|
# Writes data to the stream.
|
||||||
#
|
#
|
||||||
def write(buf, opts = {})
|
def write(buf, opts = {})
|
||||||
|
fd.syswrite(buf)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Reads data from the stream.
|
# Reads data from the stream.
|
||||||
#
|
#
|
||||||
def read(length = nil, opts = {})
|
def read(length = nil, opts = {})
|
||||||
|
length = 16384 unless length
|
||||||
|
|
||||||
|
fd.sysread(length)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Shuts down the stream for reading, writing, or both.
|
# Shuts down the stream for reading, writing, or both.
|
||||||
#
|
#
|
||||||
def shutdown(how = SW_BOTH)
|
def shutdown(how = SW_BOTH)
|
||||||
|
fd.shutdown(how)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Closes the stream and allows for resource cleanup
|
# Closes the stream and allows for resource cleanup
|
||||||
#
|
#
|
||||||
def close
|
def close
|
||||||
|
fd.close
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -58,6 +64,7 @@ module Stream
|
||||||
# true if data is available for reading, otherwise false is returned.
|
# true if data is available for reading, otherwise false is returned.
|
||||||
#
|
#
|
||||||
def has_read_data?(timeout = nil)
|
def has_read_data?(timeout = nil)
|
||||||
|
Rex::ThreadSafe.select([ fd ], nil, nil, timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -16,7 +16,9 @@ module IO
|
||||||
###
|
###
|
||||||
module StreamAbstraction
|
module StreamAbstraction
|
||||||
|
|
||||||
|
#
|
||||||
# Creates a streaming socket pair
|
# Creates a streaming socket pair
|
||||||
|
#
|
||||||
def initialize_abstraction
|
def initialize_abstraction
|
||||||
self.lsock, self.rsock = ::Socket.pair(::Socket::AF_UNIX,
|
self.lsock, self.rsock = ::Socket.pair(::Socket::AF_UNIX,
|
||||||
::Socket::SOCK_STREAM, 0)
|
::Socket::SOCK_STREAM, 0)
|
||||||
|
|
|
@ -49,7 +49,6 @@ class Channel
|
||||||
|
|
||||||
# Valid channel context?
|
# Valid channel context?
|
||||||
if (channel == nil)
|
if (channel == nil)
|
||||||
puts "nil wtf"
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -243,6 +242,26 @@ class Channel
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Enables or disables interactive mode
|
||||||
|
#
|
||||||
|
def interactive(tf = true, addends = nil)
|
||||||
|
if (self.cid == nil)
|
||||||
|
raise IOError, "Channel has been closed.", caller
|
||||||
|
end
|
||||||
|
|
||||||
|
request = Packet.create_request('core_channel_interact')
|
||||||
|
|
||||||
|
# Populate the request
|
||||||
|
request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid)
|
||||||
|
request.add_tlv(TLV_TYPE_BOOL, tf)
|
||||||
|
request.add_tlvs(addends)
|
||||||
|
|
||||||
|
self.client.send_request(request)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
# Direct I/O
|
# Direct I/O
|
||||||
|
|
|
@ -24,6 +24,8 @@ module Pools
|
||||||
###
|
###
|
||||||
class StreamPool < Rex::Post::Meterpreter::Channels::Pool
|
class StreamPool < Rex::Post::Meterpreter::Channels::Pool
|
||||||
|
|
||||||
|
include Rex::IO::StreamAbstraction
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
# Constructor
|
# Constructor
|
||||||
|
@ -33,6 +35,8 @@ class StreamPool < Rex::Post::Meterpreter::Channels::Pool
|
||||||
# Initializes the file channel instance
|
# Initializes the file channel instance
|
||||||
def initialize(client, cid, type, flags)
|
def initialize(client, cid, type, flags)
|
||||||
super(client, cid, type, flags)
|
super(client, cid, type, flags)
|
||||||
|
|
||||||
|
initialize_abstraction
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -51,6 +55,19 @@ class StreamPool < Rex::Post::Meterpreter::Channels::Pool
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dio_write_handler(packet, data)
|
||||||
|
rsock.write(data)
|
||||||
|
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
def dio_close_handler(packet)
|
||||||
|
rsock.close
|
||||||
|
|
||||||
|
return super(packet)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end; end; end; end; end
|
end; end; end; end; end
|
||||||
|
|
|
@ -63,6 +63,8 @@ class Config
|
||||||
return ifaces
|
return ifaces
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias interfaces get_interfaces
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
# Routing
|
# Routing
|
||||||
|
@ -92,6 +94,8 @@ class Config
|
||||||
return routes
|
return routes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias routes get_routes
|
||||||
|
|
||||||
# Adds a route to the target machine
|
# Adds a route to the target machine
|
||||||
def add_route(subnet, netmask, gateway)
|
def add_route(subnet, netmask, gateway)
|
||||||
request = Packet.create_request('stdapi_net_config_add_route')
|
request = Packet.create_request('stdapi_net_config_add_route')
|
||||||
|
|
|
@ -6,6 +6,24 @@ module Rex
|
||||||
module Post
|
module Post
|
||||||
module Meterpreter
|
module Meterpreter
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# RequestError
|
||||||
|
# ------------
|
||||||
|
#
|
||||||
|
# Exception thrown when a request fails.
|
||||||
|
#
|
||||||
|
###
|
||||||
|
class RequestError < ArgumentError
|
||||||
|
def initialize(method, result)
|
||||||
|
@method = method
|
||||||
|
@result = result
|
||||||
|
end
|
||||||
|
def to_s
|
||||||
|
"#{@method}: Operation failed: #{@result}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
###
|
###
|
||||||
#
|
#
|
||||||
# PacketDispatcher
|
# PacketDispatcher
|
||||||
|
@ -44,9 +62,13 @@ module PacketDispatcher
|
||||||
response = send_packet_wait_response(packet, t)
|
response = send_packet_wait_response(packet, t)
|
||||||
|
|
||||||
if (response == nil)
|
if (response == nil)
|
||||||
raise RuntimeError, packet.method + ": No response was received.", caller
|
raise TimeoutError
|
||||||
elsif (response.result != 0)
|
elsif (response.result != 0)
|
||||||
raise RuntimeError, packet.method + ": Operation failed: #{response.result}", caller
|
e = RequestError.new(packet.method, response.result)
|
||||||
|
|
||||||
|
e.set_backtrace(caller)
|
||||||
|
|
||||||
|
raise e
|
||||||
end
|
end
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -19,6 +19,7 @@ class Console
|
||||||
include Rex::Ui::Text::DispatcherShell
|
include Rex::Ui::Text::DispatcherShell
|
||||||
|
|
||||||
# Dispatchers
|
# Dispatchers
|
||||||
|
require 'rex/post/meterpreter/ui/console/interactive_channel'
|
||||||
require 'rex/post/meterpreter/ui/console/command_dispatcher'
|
require 'rex/post/meterpreter/ui/console/command_dispatcher'
|
||||||
require 'rex/post/meterpreter/ui/console/command_dispatcher/core'
|
require 'rex/post/meterpreter/ui/console/command_dispatcher/core'
|
||||||
|
|
||||||
|
@ -55,6 +56,31 @@ class Console
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Interacts with the supplied channel
|
||||||
|
#
|
||||||
|
def interact_with_channel(channel)
|
||||||
|
channel.extend(InteractiveChannel) unless (channel.kind_of?(InteractiveChannel) == true)
|
||||||
|
|
||||||
|
channel.init_ui(input, output)
|
||||||
|
channel.interact
|
||||||
|
channel.reset_ui
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Runs the specified command wrapper in something to catch meterpreter
|
||||||
|
# exceptions.
|
||||||
|
#
|
||||||
|
def run_command(dispatcher, method, arguments)
|
||||||
|
begin
|
||||||
|
super
|
||||||
|
rescue TimeoutError
|
||||||
|
output.print_line("Operation timed out.")
|
||||||
|
rescue RequestError => info
|
||||||
|
output.print_line(info.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
attr_reader :client
|
attr_reader :client
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
|
@ -60,6 +60,7 @@ class Console::CommandDispatcher::Core
|
||||||
# Displays the help menu
|
# Displays the help menu
|
||||||
#
|
#
|
||||||
def cmd_help(*args)
|
def cmd_help(*args)
|
||||||
|
print(shell.help_to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -90,11 +91,11 @@ class Console::CommandDispatcher::Core
|
||||||
md = m.downcase
|
md = m.downcase
|
||||||
|
|
||||||
if (extensions.include?(md))
|
if (extensions.include?(md))
|
||||||
print_error("The '#{m}' extension has already been loaded.")
|
print_error("The '#{md}' extension has already been loaded.")
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
print("Loading extension #{m}...")
|
print("Loading extension #{md}...")
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# Use the remote side, then load the client-side
|
# Use the remote side, then load the client-side
|
||||||
|
|
|
@ -15,16 +15,27 @@ module Ui
|
||||||
###
|
###
|
||||||
class Console::CommandDispatcher::Stdapi
|
class Console::CommandDispatcher::Stdapi
|
||||||
|
|
||||||
|
require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs'
|
||||||
|
require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net'
|
||||||
|
require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys'
|
||||||
|
|
||||||
Klass = Console::CommandDispatcher::Stdapi
|
Klass = Console::CommandDispatcher::Stdapi
|
||||||
|
|
||||||
include Console::CommandDispatcher
|
Dispatchers =
|
||||||
|
[
|
||||||
|
Klass::Fs,
|
||||||
|
Klass::Net,
|
||||||
|
Klass::Sys,
|
||||||
|
]
|
||||||
|
|
||||||
require 'rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs'
|
include Console::CommandDispatcher
|
||||||
|
|
||||||
def initialize(shell)
|
def initialize(shell)
|
||||||
super
|
super
|
||||||
|
|
||||||
shell.enstack_dispatcher(Klass::Fs)
|
Dispatchers.each { |d|
|
||||||
|
shell.enstack_dispatcher(d)
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -7,10 +7,10 @@ module Ui
|
||||||
|
|
||||||
###
|
###
|
||||||
#
|
#
|
||||||
# Stdapi
|
# Fs
|
||||||
# ------
|
# --
|
||||||
#
|
#
|
||||||
# Standard API extension.
|
# The file system portion of the standard API extension.
|
||||||
#
|
#
|
||||||
###
|
###
|
||||||
class Console::CommandDispatcher::Stdapi::Fs
|
class Console::CommandDispatcher::Stdapi::Fs
|
||||||
|
@ -207,7 +207,7 @@ class Console::CommandDispatcher::Stdapi::Fs
|
||||||
# Uploads a file or directory to the remote machine from the local
|
# Uploads a file or directory to the remote machine from the local
|
||||||
# machine.
|
# machine.
|
||||||
#
|
#
|
||||||
def cmd_download(*args)
|
def cmd_upload(*args)
|
||||||
if (args.length < 2)
|
if (args.length < 2)
|
||||||
print(
|
print(
|
||||||
"Usage: upload [options] src1 src2 src3 ... destination\n\n" +
|
"Usage: upload [options] src1 src2 src3 ... destination\n\n" +
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
require 'rex/post/meterpreter'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Ui
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# Net
|
||||||
|
# ---
|
||||||
|
#
|
||||||
|
# The networking portion of the standard API extension.
|
||||||
|
#
|
||||||
|
###
|
||||||
|
class Console::CommandDispatcher::Stdapi::Net
|
||||||
|
|
||||||
|
Klass = Console::CommandDispatcher::Stdapi::Net
|
||||||
|
|
||||||
|
include Console::CommandDispatcher
|
||||||
|
|
||||||
|
#
|
||||||
|
# Options for the generate command
|
||||||
|
#
|
||||||
|
@@route_opts = Rex::Parser::Arguments.new(
|
||||||
|
"-h" => [ false, "Help banner." ])
|
||||||
|
|
||||||
|
#
|
||||||
|
# List of supported commands
|
||||||
|
#
|
||||||
|
def commands
|
||||||
|
{
|
||||||
|
"ipconfig" => "Display interfaces",
|
||||||
|
"route" => "View and modify the routing table",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Name for this dispatcher
|
||||||
|
#
|
||||||
|
def name
|
||||||
|
"Stdapi: Networking"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Displays interfaces on the remote machine.
|
||||||
|
#
|
||||||
|
def cmd_ipconfig(*args)
|
||||||
|
ifaces = client.net.config.interfaces
|
||||||
|
|
||||||
|
if (ifaces.length == 0)
|
||||||
|
print_line("No interfaces were found.")
|
||||||
|
else
|
||||||
|
client.net.config.each_interface { |iface|
|
||||||
|
print("\n" + iface.pretty + "\n")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Displays or modifies the routing table on the remote machine.
|
||||||
|
#
|
||||||
|
def cmd_route(*args)
|
||||||
|
# Default to list
|
||||||
|
if (args.length == 0)
|
||||||
|
args.unshift("list")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check to see if they specified -h
|
||||||
|
@@route_opts.parse(args) { |opt, idx, val|
|
||||||
|
case opt
|
||||||
|
when "-h"
|
||||||
|
print(
|
||||||
|
"Usage: route [-h] command [args]\n\n" +
|
||||||
|
"Display or modify the routing table on the remote machine.\n\n" +
|
||||||
|
"Supported commands:\n\n" +
|
||||||
|
" add [subnet] [netmask] [gateway]\n" +
|
||||||
|
" delete [subnet] [netmask] [gateway]\n" +
|
||||||
|
" list\n\n")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process the commands
|
||||||
|
case args.shift
|
||||||
|
when "list"
|
||||||
|
routes = client.net.config.routes
|
||||||
|
|
||||||
|
if (routes.length == 0)
|
||||||
|
print_line("No routes were found.")
|
||||||
|
else
|
||||||
|
tbl = Rex::Ui::Text::Table.new(
|
||||||
|
'Header' => "Network routes",
|
||||||
|
'Indent' => 4,
|
||||||
|
'Columns' =>
|
||||||
|
[
|
||||||
|
"Subnet",
|
||||||
|
"Netmask",
|
||||||
|
"Gateway"
|
||||||
|
])
|
||||||
|
|
||||||
|
routes.each { |route|
|
||||||
|
tbl << [ route.subnet, route.netmask, route.gateway ]
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n" + tbl.to_s + "\n")
|
||||||
|
end
|
||||||
|
when "add"
|
||||||
|
print_line("Creating route #{args[0]}/#{args[1]} -> #{args[2]}")
|
||||||
|
|
||||||
|
client.net.config.add_route(*args)
|
||||||
|
when "delete"
|
||||||
|
print_line("Deleting route #{args[0]}/#{args[1]} -> #{args[2]}")
|
||||||
|
|
||||||
|
client.net.config.add_route(*args)
|
||||||
|
else
|
||||||
|
print_error("Unsupported command: #{args[0]}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,128 @@
|
||||||
|
require 'rex/post/meterpreter'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Ui
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# Sys
|
||||||
|
# ---
|
||||||
|
#
|
||||||
|
# The system level portion of the standard API extension.
|
||||||
|
#
|
||||||
|
###
|
||||||
|
class Console::CommandDispatcher::Stdapi::Sys
|
||||||
|
|
||||||
|
Klass = Console::CommandDispatcher::Stdapi::Sys
|
||||||
|
|
||||||
|
include Console::CommandDispatcher
|
||||||
|
|
||||||
|
@@execute_opts = Rex::Parser::Arguments.new(
|
||||||
|
"-a" => [ true, "The arguments to pass to the command." ],
|
||||||
|
"-c" => [ false, "Channelized I/O (required for interaction)." ],
|
||||||
|
"-f" => [ true, "The executable command to run." ],
|
||||||
|
"-h" => [ false, "Help menu." ],
|
||||||
|
"-H" => [ false, "Create the process hidden from view." ],
|
||||||
|
"-i" => [ false, "Interact with the process after creating it." ])
|
||||||
|
|
||||||
|
#
|
||||||
|
# List of supported commands
|
||||||
|
#
|
||||||
|
def commands
|
||||||
|
{
|
||||||
|
"ps" => "List running processes",
|
||||||
|
"execute" => "Execute a command",
|
||||||
|
"kill" => "Terminate a process",
|
||||||
|
"getpid" => "Get the current process identifier",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Name for this dispatcher
|
||||||
|
#
|
||||||
|
def name
|
||||||
|
"Stdapi: System"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Executes a command with some options.
|
||||||
|
#
|
||||||
|
def cmd_execute(*args)
|
||||||
|
if (args.length == 0)
|
||||||
|
args.unshift("-h")
|
||||||
|
end
|
||||||
|
|
||||||
|
interact = false
|
||||||
|
channelized = nil
|
||||||
|
hidden = nil
|
||||||
|
cmd_args = nil
|
||||||
|
cmd_exec = nil
|
||||||
|
|
||||||
|
@@execute_opts.parse(args) { |opt, idx, val|
|
||||||
|
case opt
|
||||||
|
when "-a"
|
||||||
|
cmd_args = val
|
||||||
|
when "-c"
|
||||||
|
channelized = true
|
||||||
|
when "-f"
|
||||||
|
cmd_exec = val
|
||||||
|
when "-H"
|
||||||
|
hidden = true
|
||||||
|
when "-h"
|
||||||
|
print(
|
||||||
|
"Usage: execute -f file [options]\n\n" +
|
||||||
|
"Executes a command on the remote machine.\n" +
|
||||||
|
@@execute_opts.usage)
|
||||||
|
return true
|
||||||
|
when "-i"
|
||||||
|
channelized = true
|
||||||
|
interact = true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
# Did we at least get an executable?
|
||||||
|
if (cmd_exec == nil)
|
||||||
|
print_error("You must specify an executable file with -f")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Execute it
|
||||||
|
p = client.sys.process.execute(cmd_exec, cmd_args,
|
||||||
|
'Channelized' => channelized,
|
||||||
|
'Hidden' => hidden)
|
||||||
|
|
||||||
|
print_line("Process #{p.pid} created.")
|
||||||
|
print_line("Channel #{p.channel.cid} created.") if (p.channel)
|
||||||
|
|
||||||
|
if (interact and p.channel)
|
||||||
|
shell.interact_with_channel(p.channel)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets the process identifier that meterpreter is running in on the remote
|
||||||
|
# machine.
|
||||||
|
#
|
||||||
|
def cmd_getpid(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Kills one or more processes.
|
||||||
|
#
|
||||||
|
def cmd_kill(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Lists running processes
|
||||||
|
#
|
||||||
|
def cmd_ps(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,94 @@
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Ui
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# InteractiveChannel
|
||||||
|
# ------------------
|
||||||
|
#
|
||||||
|
# Mixin that is meant to extend the base channel class from meterpreter in a
|
||||||
|
# manner that adds interactive capabilities.
|
||||||
|
#
|
||||||
|
###
|
||||||
|
module Console::InteractiveChannel
|
||||||
|
|
||||||
|
include Rex::Ui::Interactive
|
||||||
|
|
||||||
|
#
|
||||||
|
# Interacts with self.
|
||||||
|
#
|
||||||
|
def _interact
|
||||||
|
# If the channel has a left-side socket, then we can interact with it.
|
||||||
|
if (self.lsock)
|
||||||
|
self.interactive(true)
|
||||||
|
|
||||||
|
begin
|
||||||
|
interact_stream(self)
|
||||||
|
rescue
|
||||||
|
end
|
||||||
|
|
||||||
|
self.interactive(false)
|
||||||
|
else
|
||||||
|
print_error("Channel #{self.cid} does not support interaction.")
|
||||||
|
|
||||||
|
self.interacting = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Called when an interrupt is sent
|
||||||
|
#
|
||||||
|
def _interrupt
|
||||||
|
prompt_yesno("Terminate channel #{self.cid}?")
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
def _suspend
|
||||||
|
# Ask the user if they would like to background the session
|
||||||
|
if (prompt_yesno("Background channel #{self.cid}?") == true)
|
||||||
|
self.interacting = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Closes the channel like it aint no thang.
|
||||||
|
#
|
||||||
|
def _interact_complete
|
||||||
|
self.close
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Reads data from local input and writes it remotely.
|
||||||
|
#
|
||||||
|
def _stream_read_local_write_remote(channel)
|
||||||
|
data = user_input.gets
|
||||||
|
|
||||||
|
self.write(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Reads from the channel and writes locally.
|
||||||
|
#
|
||||||
|
def _stream_read_remote_write_local(channel)
|
||||||
|
data = channel.read
|
||||||
|
|
||||||
|
user_output.print(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns the remote file descriptor to select on
|
||||||
|
#
|
||||||
|
def _remote_fd
|
||||||
|
self.lsock
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,3 +17,4 @@ require 'rex/ui/text/table'
|
||||||
|
|
||||||
# Ui subscriber
|
# Ui subscriber
|
||||||
require 'rex/ui/subscriber'
|
require 'rex/ui/subscriber'
|
||||||
|
require 'rex/ui/interactive'
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
module Rex
|
||||||
|
module Ui
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# Interactive
|
||||||
|
# -----------
|
||||||
|
#
|
||||||
|
# This class implements the stubs that are needed to provide an interactive
|
||||||
|
# user interface that is backed against something arbitrary.
|
||||||
|
#
|
||||||
|
###
|
||||||
|
module Interactive
|
||||||
|
|
||||||
|
#
|
||||||
|
# Interactive sessions by default may interact with the local user input
|
||||||
|
# and output.
|
||||||
|
#
|
||||||
|
include Rex::Ui::Subscriber
|
||||||
|
|
||||||
|
#
|
||||||
|
# Starts interacting with the session at the most raw level, simply
|
||||||
|
# forwarding input from user_input to rstream and forwarding input from
|
||||||
|
# rstream to user_output.
|
||||||
|
#
|
||||||
|
def interact
|
||||||
|
self.interacting = true
|
||||||
|
|
||||||
|
eof = false
|
||||||
|
|
||||||
|
# Handle suspend notifications
|
||||||
|
handle_suspend
|
||||||
|
|
||||||
|
callcc { |ctx|
|
||||||
|
# As long as we're interacting...
|
||||||
|
while (self.interacting == true)
|
||||||
|
begin
|
||||||
|
_interact
|
||||||
|
# If we get an interrupt exception, ask the user if they want to
|
||||||
|
# abort the interaction. If they do, then we return out of
|
||||||
|
# the interact function and call it a day.
|
||||||
|
rescue Interrupt
|
||||||
|
if (_interrupt)
|
||||||
|
eof = true
|
||||||
|
ctx.call
|
||||||
|
end
|
||||||
|
# If we reach EOF or the connection is reset...
|
||||||
|
rescue EOFError, Errno::ECONNRESET
|
||||||
|
eof = true
|
||||||
|
ctx.call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restore the suspend handler
|
||||||
|
restore_suspend
|
||||||
|
|
||||||
|
# If we've hit eof, call the interact complete handler
|
||||||
|
_interact_complete if (eof == true)
|
||||||
|
|
||||||
|
# Return whether or not EOF was reached
|
||||||
|
return eof
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Whether or not the session is currently being interacted with
|
||||||
|
#
|
||||||
|
attr_reader :interacting
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
attr_writer :interacting
|
||||||
|
#
|
||||||
|
# The original suspend proc.
|
||||||
|
#
|
||||||
|
attr_accessor :orig_suspend
|
||||||
|
|
||||||
|
#
|
||||||
|
# Stub method that is meant to handler interaction
|
||||||
|
#
|
||||||
|
def _interact
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Called when an interrupt is sent.
|
||||||
|
#
|
||||||
|
def _interrupt
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Called when a suspend is sent.
|
||||||
|
#
|
||||||
|
def _suspend
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Called when interaction has completed and one of the sides has closed.
|
||||||
|
#
|
||||||
|
def _interact_complete
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read from remote and write to local.
|
||||||
|
#
|
||||||
|
def _stream_read_remote_write_local(stream)
|
||||||
|
data = stream.get
|
||||||
|
|
||||||
|
user_output.print(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read from local and write to remote.
|
||||||
|
#
|
||||||
|
def _stream_read_local_write_remote(stream)
|
||||||
|
data = user_input.gets
|
||||||
|
|
||||||
|
stream.put(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def _local_fd
|
||||||
|
user_input.fd
|
||||||
|
end
|
||||||
|
|
||||||
|
def _remote_fd(stream)
|
||||||
|
stream.fd
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Interacts with two streaming connections, reading data from one and
|
||||||
|
# writing it to the other. Both are expected to implement Rex::IO::Stream.
|
||||||
|
#
|
||||||
|
def interact_stream(stream)
|
||||||
|
while self.interacting
|
||||||
|
# Select input and rstream
|
||||||
|
sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ])
|
||||||
|
|
||||||
|
# Cycle through the items that have data
|
||||||
|
# From the stream? Write to user_output.
|
||||||
|
sd[0].each { |s|
|
||||||
|
if (s == _remote_fd(stream))
|
||||||
|
_stream_read_remote_write_local(stream)
|
||||||
|
# From user_input? Write to stream.
|
||||||
|
elsif (s == _local_fd)
|
||||||
|
_stream_read_local_write_remote(stream)
|
||||||
|
end
|
||||||
|
} if (sd)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Installs a signal handler to monitor suspend signal notifications.
|
||||||
|
#
|
||||||
|
def handle_suspend
|
||||||
|
if (orig_suspend == nil)
|
||||||
|
self.orig_suspend = Signal.trap("TSTP") {
|
||||||
|
_suspend
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Restores the previously installed signal handler for suspend
|
||||||
|
# notifications.
|
||||||
|
#
|
||||||
|
def restore_suspend
|
||||||
|
if (orig_suspend)
|
||||||
|
Signal.trap("TSTP", orig_suspend)
|
||||||
|
|
||||||
|
self.orig_suspend = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Prompt the user for input if possible.
|
||||||
|
#
|
||||||
|
def prompt(query)
|
||||||
|
if (user_output and user_input)
|
||||||
|
user_output.print("\n" + query)
|
||||||
|
user_input.gets
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check the return value of a yes/no prompt
|
||||||
|
#
|
||||||
|
def prompt_yesno(query)
|
||||||
|
(prompt(query + " [y/N] ") =~ /^y/i) ? true : false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -101,7 +101,9 @@ module DispatcherShell
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
# Run a single command line
|
# Run a single command line
|
||||||
|
#
|
||||||
def run_single(line)
|
def run_single(line)
|
||||||
arguments = parse_line(line)
|
arguments = parse_line(line)
|
||||||
method = arguments.shift
|
method = arguments.shift
|
||||||
|
@ -115,10 +117,9 @@ module DispatcherShell
|
||||||
dispatcher_stack.each { |dispatcher|
|
dispatcher_stack.each { |dispatcher|
|
||||||
begin
|
begin
|
||||||
if (dispatcher.respond_to?('cmd_' + method))
|
if (dispatcher.respond_to?('cmd_' + method))
|
||||||
|
run_command(dispatcher, method, arguments)
|
||||||
|
|
||||||
found = true
|
found = true
|
||||||
eval("
|
|
||||||
dispatcher.#{'cmd_' + method}(*arguments)
|
|
||||||
")
|
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
output.print_error(
|
output.print_error(
|
||||||
|
@ -139,6 +140,13 @@ module DispatcherShell
|
||||||
return found
|
return found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Runs the supplied command on the given dispatcher.
|
||||||
|
#
|
||||||
|
def run_command(dispatcher, method, arguments)
|
||||||
|
eval("dispatcher.#{'cmd_' + method}(*arguments)")
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# If the command is unknown...
|
# If the command is unknown...
|
||||||
#
|
#
|
||||||
|
@ -146,16 +154,58 @@ module DispatcherShell
|
||||||
output.print_error("Unknown command: #{method}.")
|
output.print_error("Unknown command: #{method}.")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
# Push a dispatcher to the front of the stack
|
# Push a dispatcher to the front of the stack
|
||||||
|
#
|
||||||
def enstack_dispatcher(dispatcher)
|
def enstack_dispatcher(dispatcher)
|
||||||
self.dispatcher_stack.unshift(dispatcher.new(self))
|
self.dispatcher_stack.unshift(dispatcher.new(self))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
# Pop a dispatcher from the front of the stacker
|
# Pop a dispatcher from the front of the stacker
|
||||||
|
#
|
||||||
def destack_dispatcher
|
def destack_dispatcher
|
||||||
self.dispatcher_stack.shift
|
self.dispatcher_stack.shift
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return a readable version of a help banner for all of the enstacked
|
||||||
|
# dispatchers.
|
||||||
|
#
|
||||||
|
def help_to_s(opts = {})
|
||||||
|
str = ''
|
||||||
|
|
||||||
|
dispatcher_stack.reverse.each { |dispatcher|
|
||||||
|
# No commands? Suckage.
|
||||||
|
next if ((dispatcher.respond_to?('commands') == false) or
|
||||||
|
(dispatcher.commands.length == 0))
|
||||||
|
|
||||||
|
# Display the commands
|
||||||
|
tbl = Table.new(
|
||||||
|
'Header' => "#{dispatcher.name} Commands",
|
||||||
|
'Indent' => opts['Indent'] || 4,
|
||||||
|
'Columns' =>
|
||||||
|
[
|
||||||
|
'Command',
|
||||||
|
'Description'
|
||||||
|
],
|
||||||
|
'ColProps' =>
|
||||||
|
{
|
||||||
|
'Command' =>
|
||||||
|
{
|
||||||
|
'MaxWidth' => 12
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatcher.commands.sort.each { |c|
|
||||||
|
tbl << c
|
||||||
|
}
|
||||||
|
|
||||||
|
str += "\n" + tbl.to_s + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
attr_accessor :dispatcher_stack
|
attr_accessor :dispatcher_stack
|
||||||
|
|
Loading…
Reference in New Issue