it lives, major changes, fixed bugs, exploiting works with the test exploit

git-svn-id: file:///home/svn/incoming/trunk@2763 4d416f70-5f16-0410-b530-b9f4589650da
unstable
Matt Miller 2005-07-16 07:32:11 +00:00
parent be2414a8b2
commit 2f2363d141
29 changed files with 728 additions and 62 deletions

View File

@ -25,10 +25,7 @@ require 'msf/base/config'
require 'msf/base/simple'
# Sessions
require 'msf/base/session/command_shell'
require 'msf/base/session/meterpreter'
require 'msf/base/session/dispatch_ninja'
require 'msf/base/session/vnc'
require 'msf/base/sessions/command_shell'
# Serialization
require 'msf/base/serializer/readable_text'

View File

@ -0,0 +1,59 @@
require 'msf/base'
module Msf
module Sessions
###
#
# CommandShell
# ------------
#
# This class provides basic interaction with a command shell on the remote
# endpoint. This session is initialized with a stream that will be used
# as the pipe for reading and writing the command shell.
#
###
class CommandShell
#
# This interface supports basic interaction.
#
include Msf::Session::Basic
#
# This interface supports interacting with a single command shell.
#
include Msf::Session::Provider::SingleCommandShell
#
# The shell will have been initialized by default
#
def init_shell
return true
end
#
# Read from the command shell
#
def read_shell(length = nil)
return rstream.read(length)
end
#
# Writes to the command shell
#
def write_shell(buf)
rstream.write(buf)
end
#
# Closes the shell
#
def close_shell()
rstream.close
end
end
end
end

View File

@ -0,0 +1,9 @@
#!/usr/bin/ruby
require 'test/unit'
require 'msf/base'
class Msf::Sessions::CommandShell::UnitTest < Test::Unit::TestCase
def test_cmdshell
end
end

View File

@ -206,6 +206,9 @@ class Exploit < Msf::Module
# Set the encoded payload to the result of the encoding process
self.payload = EncodedPayload.create(real_payload, reqs)
# Save the payload instance
self.payload_instance = real_payload
return self.payload
end

View File

@ -28,8 +28,8 @@ module Exploit::Remote::Tcp
nsock = Rex::Socket::Tcp.create(
'PeerHost' => datastore['RHOST'],
'PeerPort' => datastore['RPORT'].to_i,
'LocalHost' => datastore['LHOST'] || "0.0.0.0",
'LocalPort' => datastore['LPORT'] ? datastore['LPORT'].to_i : 0)
'LocalHost' => datastore['CHOST'] || "0.0.0.0",
'LocalPort' => datastore['CPORT'] ? datastore['CPORT'].to_i : 0)
# Set this socket to the global socket as necessary
self.sock = nsock if (global)

View File

@ -104,6 +104,9 @@ class ExploitDriver
# module instance
exploit.generate_payload(payload)
# Default the session to nil
session = nil
begin
# Set the exploit up the bomb
exploit.setup
@ -111,8 +114,8 @@ class ExploitDriver
# Launch the exploit
exploit.exploit
# Give the payload some extra time after the exploit completes
payload.extra_delay
# Wait the payload to acquire a session
session = payload.wait_for_session
ensure
# Ensure that, no matter what, clean up of the handler occurs
payload.stop_handler
@ -121,7 +124,7 @@ class ExploitDriver
exploit.cleanup
end
return nil
return session
end
attr_reader :target_idx

View File

@ -45,6 +45,17 @@ module Handler
return "none"
end
#
# Initializes the session waiter event and other fun stuff.
#
def initialize(info = {})
super
# Create the waiter event with auto_reset set to false so that
# if a session is ever created, waiting on it returns immediately.
self.session_waiter_event = Rex::Sync::Event.new(false, false)
end
#
# Sets up the connection handler
#
@ -81,20 +92,61 @@ module Handler
# Handles an established connection supplied in the in and out
# handles. The handles are passed as parameters in case this
# handler is capable of handling multiple simultaneous
# connections.
# connections. The default implementation simply creates a
# session using the payload's session factory reference and
# the supplied stream.
#
def handle_connection(stream)
def handle_connection(conn)
# If the payload we merged in with has an associated session factory,
# allocate a new session.
if (self.session)
s = self.session.new(conn)
# If the session is valid, register it with the framework and
# notify any waiters we may have.
if (s)
register_session(s)
end
end
end
#
# Wait just one second there!
# The amount of time to wait for a session to come in.
#
def extra_delay
sleep(1)
def wfs_delay
1
end
#
# Waits for a session to be created as the result of a handler connection
# coming in. The return value is a session object instance on success or
# nil if the timeout expires
#
def wait_for_session(t = wfs_delay)
session = nil
begin
session = session_waiter_event.wait(t)
rescue ::TimeoutError
end
return session
end
protected
#
# Registers a session with the framework and notifies any waiters of the
# new session.
#
def register_session(session)
session_waiter_event.notify(session)
# TODO: register with the framework
end
attr_accessor :session_waiter_event
end
end

View File

@ -40,20 +40,19 @@ module ReverseTcp
# if it fails to start the listener
#
def setup_handler
listener_sock = comm.create(
self.listener_sock = Rex::Socket::TcpServer.create(
'LocalHost' => datastore['LHOST'],
'LocalPort' => datastore['LPORT'].to_i,
'Server' => true,
'Proto' => 'tcp')
'Comm' => comm)
end
#
# Closes the listener socket if one was created
#
def cleanup_handler
if (listener_sock)
listener_sock.close
listener_sock = nil
if (self.listener_sock)
self.listener_sock.close
self.listener_sock = nil
end
# Kill any remaining handle_connection threads that might
@ -68,13 +67,15 @@ module ReverseTcp
#
def start_handler
listener_thread = Thread.new {
client = nil
# Accept a client connection
begin
client = listener_sock.accept
client = self.listener_sock.accept
rescue
wlog("Exception raised during listener accept: #{$!}")
return nil
end
# Start a new thread and pass the client connection
# as the input and output pipe. Client's are expected
# to implement the Stream interface.
@ -89,9 +90,9 @@ module ReverseTcp
#
def stop_handler
# Terminate the listener thread
if (listener_thread and listener_thread.alive? == true)
listener_thread.kill
listener_thread = nil
if (self.listener_thread and self.listener_thread.alive? == true)
self.listener_thread.kill
self.listener_thread = nil
end
end

View File

@ -96,6 +96,14 @@ class Payload < Msf::Module
return module_info['Handler'] || Msf::Handler
end
#
# Returns the session class that is associated with this payload and will
# be used to create a session as necessary.
#
def session
return module_info['Session']
end
##
#
# Generation & variable substitution

View File

@ -28,7 +28,7 @@ end
# -------
#
# The session class represents a post-exploitation, uh, session.
# Sessions can be written from, read to, and interacted with. The
# Sessions can be written to, read from, and interacted with. The
# underlying medium on which they are backed is arbitrary. For
# instance, when an exploit is provided with a command shell,
# either through a network connection or locally, the session's
@ -39,9 +39,25 @@ end
# tied to a network connection.
#
###
class Session
module Session
def initialize()
include Framework::Offspring
# Direct descendents
require 'msf/core/session/interactive'
require 'msf/core/session/basic'
# Provider interfaces
require 'msf/core/session/provider/single_command_execution'
require 'msf/core/session/provider/multi_command_execution'
require 'msf/core/session/provider/single_command_shell'
require 'msf/core/session/provider/multi_command_shell'
#
# By default, sessions are not interactive.
#
def interactive?
false
end
#
@ -50,18 +66,25 @@ class Session
def cleanup
end
attr_accessor :framework, :sid
#
# Returns the session's name if it's been assigned one, otherwise
# the sid is returned.
#
def sname
return name || sid
end
#
# Sets the session's name
#
def sname=(name)
self.name = name
end
attr_accessor :framework, :sid, :name
protected
end
end
#
# Require the individual provider interfaces
#
require 'msf/core/session_provider/single_command_execution'
require 'msf/core/session_provider/multi_command_execution'
require 'msf/core/session_provider/single_command_shell'
require 'msf/core/session_provider/multi_command_shell'

View File

@ -0,0 +1,113 @@
module Msf
module Session
###
#
# Basic
# -----
#
# This class implements an interactive session using raw input/output in
# only the most basic fashion.
#
###
module Basic
include Session
include Interactive
#
# Initialize's the raw session
#
def initialize(rstream)
self.rstream = rstream
end
#
# Returns that, yes, indeed, this session supports going interactive with
# the user.
#
def interactive?
true
end
#
# Closes rstream.
#
def cleanup
rstream.close if (rstream)
rstream = nil
end
#
# Starts interacting with the session at the most raw level, simply
# forwarding input from linput to rstream and forwarding input from
# rstream to loutput.
#
def interact
callcc { |ctx|
while 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
loutput.print("\nStop interacting with session #{sname}? [y/N] ")
r = linput.gets
# Break out of the continuation
ctx.call if (r =~ /^y/i)
rescue EOFError
loutput.print_line("Session #{sname} terminating...")
ctx.call
end
end
}
end
#
# The local input handle. Must inherit from Rex::Ui::Text::Input.
#
attr_accessor :linput
#
# The local output handle. Must inherit from Rex::Ui::Output.
#
attr_accessor :loutput
#
# The remote stream handle. Must inherit from Rex::IO::Stream.
#
attr_accessor :rstream
protected
#
# Performs the actual raw interaction with the remote side. This can be
# overriden by derived classes if they wish to do this another way.
#
def _interact
while true
# Select input and rstream
sd = select([ linput.fd, rstream.fd ], nil, nil, 0.5)
# Cycle through the items that have data
# From the rstream? Write to linput.
sd[0].each { |s|
if (s == rstream.fd)
data = rstream.get
loutput.print(data)
# From linput? Write to rstream.
elsif (s == linput.fd)
data = linput.gets
rstream.put(data)
end
} if (sd)
end
end
end
end
end

View File

@ -0,0 +1,41 @@
module Msf
module Session
###
#
# Interactive
# -----------
#
# This class implements the stubs that are needed to provide an interactive
# session.
#
###
module Interactive
#
# Returns that, yes, indeed, this session supports going interactive with
# the user.
#
def interactive?
true
end
#
# Starts interacting with the session.
#
def interact
end
#
# The local input handle. Must inherit from Rex::Ui::Text::Input.
#
attr_accessor :linput
#
# The local output handle. Must inherit from Rex::Ui::Output.
#
attr_accessor :loutput
end
end
end

View File

@ -0,0 +1,58 @@
module Msf
module Session
module Provider
###
#
# MultiCommandExecution
# ---------------------
#
# Executes multiple commands and optionally allows for reading/writing I/O
# to the new processes.
#
###
module MultiCommandExecution
#
# Initializes the single command instance that will be used
# implicitly if no command is supplied to any of the functions.
#
def init_cmd(command, arguments = nil, opts = nil)
end
#
# Executes a command with the supplied options, returning a context
# that should be supplied to future calls. Supported options:
#
# - Hidden
# Launch the command hidden from view.
#
def exec_cmd(command, arguments = nil, opts = nil)
end
#
# Reads output from a command. If no command is supplied, the default
# command is used.
#
def read_cmd(length = nil, cmd = nil)
end
#
# Writes input to a command. If no command is supplied, the default
# command is used.
#
def write_cmd(buf, cmd = nil)
end
#
# Closes a command that was executed. If no command is supplied, the
# default command is used.
#
def close_cmd(cmd = nil)
end
end
end
end
end

View File

@ -0,0 +1,64 @@
module Msf
module Session
module Provider
###
#
# MultiCommandShell
# -----------------
#
# This interface is to be implemented by a session that is capable of
# providing multiple command shell interfaces simultaneously. Inherently,
# MultiCommandShell classes must also provide a mechanism by which they can
# implement the SingleCommandShell interface.
#
#
###
module MultiCommandShell
include SingleCommandShell
#
# Initializes the default command shell as expected from
# SingleCommandShell
#
def init_shell()
raise NotImplementedError
end
#
# Opens a new command shell context and returns the handle
#
def open_shell()
raise NotImplementedError
end
#
# Reads data from a command shell. If shell is nil, the default
# command shell from init_shell is used.
#
def read_shell(length = nil, shell = nil)
raise NotImplementedError
end
#
# Writes data to a command shell. If shell is nil, the default
# command shell from init_shell is used.
#
def write_shell(buf, shell = nil)
raise NotImplementedError
end
#
# Closes the provided command shell or the default one if none is
# given.
#
def close_shell(shell = nil)
raise NotImplementedError
end
end
end
end
end

View File

@ -0,0 +1,44 @@
module Msf
module Session
module Provider
###
#
# SingleCommandExecution
# ----------------------
#
# Executes a single command and optionally allows for reading/writing I/O
# to the new process.
#
###
module SingleCommandExecution
#
# Initializes the executed command for reading/writing.
#
def init_cmd(command, arguments = nil, opts = nil)
end
#
# Reads output from the command
#
def read_cmd(length = nil)
end
#
# Writes input to the command
#
def write_cmd(buf)
end
#
# Closes the command that was executed
#
def close_cmd()
end
end
end
end
end

View File

@ -0,0 +1,48 @@
module Msf
module Session
module Provider
###
#
# SingleCommandShell
# ------------------
#
# This interface is to be implemented by a session that is only capable of
# providing an interface to a single command shell.
#
###
module SingleCommandShell
#
# Initializes the command shell
#
def init_shell()
raise NotImplementedError
end
#
# Reads data from the command shell
#
def read_shell(length = nil)
raise NotImplementedError
end
#
# Writes data to the command shell
#
def write_shell(buf)
raise NotImplementedError
end
#
# Closes the command shell
#
def close_shell()
raise NotImplementedError
end
end
end
end
end

View File

@ -110,8 +110,8 @@ class Exploit
# interacted with, start interacting with it
if (bg == false and session.interactive?)
# Set the session's input and output handles
session.local_input = driver.input
session.local_output = driver.output
session.linput = driver.input
session.loutput = driver.output
# Interact
session.interact()

View File

@ -8,6 +8,7 @@ require 'rex/read_write_lock'
require 'rex/transformer'
require 'rex/text'
require 'rex/string_utils'
require 'rex/sync/event'
# Encoding
require 'rex/encoder/xor'
@ -27,6 +28,7 @@ require 'rex/io/stream_server'
require 'rex/socket'
require 'rex/socket/parameters'
require 'rex/socket/tcp'
require 'rex/socket/tcp_server'
require 'rex/socket/comm/local'
# Parsers

View File

@ -21,42 +21,36 @@ module Stream
# Set the stream to blocking or non-blocking
#
def blocking=(tf)
super
end
#
# Check to see if the stream is blocking or non-blocking
#
def blocking
super
end
#
# Writes data to the stream.
#
def write(buf, opts = {})
super
end
#
# Reads data from the stream.
#
def read(length = nil, opts = {})
super
end
#
# Shuts down the stream for reading, writing, or both.
#
def shutdown(how = SW_BOTH)
super
end
#
# Closes the stream and allows for resource cleanup
#
def close
super
end
#
@ -64,14 +58,19 @@ module Stream
# true if data is available for reading, otherwise false is returned.
#
def has_read_data?(timeout = nil)
super
end
#
# Returns the file descriptor that can be polled via select, if any.
#
def poll_fd
super
end
#
# Wrapper for poll_fd
#
def fd
poll_fd
end
##
@ -141,7 +140,7 @@ module Stream
end
# No data in the first place? bust.
if (!has_read_data?(timeout))
if (has_read_data?(timeout) == false)
return nil
end
@ -149,10 +148,15 @@ module Stream
lps = 0
# Keep looping until there is no more data to be gotten..
while (has_read_data?(ltimeout))
while (has_read_data?(ltimeout) == true)
temp = read(def_block_size)
break if (temp == nil or temp.empty?)
# If we read zero bytes and we had data, then we've hit EOF
if (temp and temp.length == 0)
raise EOFError
end
break if (temp == nil or temp.empty? == true)
buf += temp
lps += 1

View File

@ -41,6 +41,8 @@ class Rex::Socket::Comm::Local
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
end
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
sock.bind(Rex::Socket.to_sockaddr(param.localhost, param.localport))
rescue Errno::EADDRINUSE
raise Rex::AddressInUse.new(param.localhost, param.localport), caller

View File

@ -43,24 +43,31 @@ class Rex::Socket::Tcp < Rex::Socket
return sock.syswrite(buf)
end
#
# Raises EOFError if it reaches end-of-file
#
def read(length = nil, opts = {})
length = 16384 unless length
begin
return sock.sysread(length)
rescue EOFError
return nil
end
return sock.sysread(length)
end
def shutdown(how = SHUT_RDWR)
return (sock.shutdown(how) == 0)
end
def has_read_data?(timeout = nil)
def has_read_data?(timeout = 0)
timeout = timeout.to_i if (timeout)
return (select([ poll_fd ], nil, nil, timeout) != nil)
end
def close
self.sock.close if (self.sock)
end
def poll_fd
return self.sock
end
end

View File

@ -21,6 +21,13 @@ class Rex::Socket::TcpServer < Rex::Socket
#
##
#
# Creates the server using the supplied hash
#
def self.create(hash)
self.create_param(Rex::Socket::Parameters.from_hash(hash))
end
#
# Wrapper around the base class' creation method that automatically sets
# the parameter's protocol to TCP and sets the server flag to true
@ -48,7 +55,7 @@ class Rex::Socket::TcpServer < Rex::Socket
# Accepts a child connection
#
def accept(opts = {})
return Rex::Socket::Tcp.new(sock.accept[0])
Rex::Socket::Tcp.new(self.sock.accept[0])
end
#

83
lib/rex/sync/event.rb Normal file
View File

@ -0,0 +1,83 @@
require 'thread'
module Rex
module Sync
###
#
# Event
# -----
#
# This class wraps the logical ConditionVariable class to make it an easier to
# work with interface that is similar to Windows' synchronization events.
#
###
class Event
Infinite = -1
def initialize(state = false, auto_reset = true, param = nil)
self.state = state
self.auto_reset = auto_reset
self.param = param
self.mutex = Mutex.new
self.cond = ConditionVariable.new
end
#
# Sets the event and wakes up anyone who was waiting.
#
def set(param = nil)
self.param = param
self.mutex.synchronize {
# If this event does not automatically reset its state,
# set the state to true
if (auto_reset == false)
self.state = true
end
self.cond.broadcast
}
end
#
# Resets the signaled state to false.
#
def reset
self.param = nil
self.state = false
end
#
# Alias notify with set
#
alias notify set
#
# Waits for the event to become signaled. Timeout is measured in
# seconds. Raises TimeoutError if the condition does not become signaled.
#
def wait(t = Infinite)
callcc { |ctx|
self.mutex.synchronize {
ctx.call if (self.state == true)
timeout(t) {
self.cond.wait(self.mutex)
}
}
}
return self.param
end
protected
attr_accessor :state, :auto_reset
attr_accessor :param, :mutex, :cond
end
end
end

View File

@ -37,6 +37,14 @@ class Input
return eof
end
#
# Returns a pollable file descriptor that is associated with this
# input medium.
#
def fd
raise NotImplementedError
end
#
# Indicates whether or not this input medium is intrinsicly a
# shell provider. This would indicate whether or not it

View File

@ -25,7 +25,17 @@ begin
end
end
#
# Regular old gets
#
def gets
self.fd.gets
end
#
# Prompt-based getline
#
def pgets
if ((line = readline(prompt, true)))
HISTORY.pop if (line.empty?)
return line + "\n"
@ -35,6 +45,13 @@ begin
end
end
#
# Returns the $stdin handle.
#
def fd
$stdin
end
#
# Indicates that this input medium as a shell builtin, no need
# to extend.

View File

@ -20,6 +20,10 @@ class Input::Stdio < Rex::Ui::Text::Input
def eof?
return $stdin.eof?
end
def fd
return $stdin
end
end
end

View File

@ -27,9 +27,9 @@ module Shell
module InputShell
attr_accessor :prompt, :output
def gets
def pgets
output.print(prompt)
super
gets
end
end
@ -75,7 +75,7 @@ module Shell
def run
stop_flag = false
while ((line = input.gets))
while ((line = input.pgets))
run_single(line)
break if (input.eof? or self.stop_flag)
@ -175,6 +175,7 @@ module Shell
end
attr_accessor :disable_output
attr_reader :input, :output
protected
@ -194,7 +195,8 @@ protected
end
attr_accessor :input, :output, :stop_flag, :init_prompt
attr_writer :input, :output
attr_accessor :stop_flag, :init_prompt
attr_accessor :prompt_char, :tab_complete_proc
end

View File

@ -38,6 +38,11 @@ class Exploits::Test::Multi::Aggressive < Msf::Exploit::Remote
def exploit
connect
#puts "raw:"
#puts Rex::Text.to_c(payload.raw)
#puts "encoded:"
#puts Rex::Text.to_c(payload.encoded)
sock.put(payload.encoded)
handler

View File

@ -1,5 +1,6 @@
require 'msf/core'
require 'msf/core/handler/reverse_tcp'
require 'msf/base/sessions/command_shell'
module Msf
module Payloads
@ -20,6 +21,7 @@ module Shell
'Platform' => 'linux',
'Arch' => ARCH_X86,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::CommandShell,
'Payload' =>
{
'Offsets' =>