From 5e9fdc28e70e6d03c5d5162d54c58ca5b5e7cf92 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 31 Jan 2011 05:20:16 +0000 Subject: [PATCH] move shell token stuff up to a mixin so meterpreter can use it, too git-svn-id: file:///home/svn/framework3/trunk@11682 4d416f70-5f16-0410-b530-b9f4589650da --- lib/msf/base/sessions/command_shell.rb | 69 ---------------- lib/msf/base/sessions/meterpreter.rb | 80 ++++++++++++++++++- .../session/provider/single_command_shell.rb | 77 ++++++++++++++++++ 3 files changed, 156 insertions(+), 70 deletions(-) diff --git a/lib/msf/base/sessions/command_shell.rb b/lib/msf/base/sessions/command_shell.rb index 5091afa64a..e42e513649 100644 --- a/lib/msf/base/sessions/command_shell.rb +++ b/lib/msf/base/sessions/command_shell.rb @@ -99,75 +99,6 @@ class CommandShell buff end - - # - # Read data until we find the token - # - def shell_read_until_token(token, wanted_idx = 0, timeout = 10) - if (wanted_idx == 0) - parts_needed = 2 - else - parts_needed = 1 + (wanted_idx * 2) - end - - # Read until we get the data between two tokens or absolute timeout. - begin - ::Timeout.timeout(timeout) do - buf = '' - idx = nil - loop do - if (tmp = shell_read(-1, 2)) - buf << tmp - - # see if we have the wanted idx - parts = buf.split(token, -1) - if (parts.length == parts_needed) - # cause another prompt to appear (just in case) - shell_write("\n") - return parts[wanted_idx] - end - end - end - end - rescue - # nothing, just continue - end - - # failed to get any data or find the token! - nil - end - - # - # Explicitly run a single command and return the output. - # This version uses a marker to denote the end of data (instead of a timeout). - # - def shell_command_token_unix(cmd) - # read any pending data - buf = shell_read(-1, 0.01) - token = ::Rex::Text.rand_text_alpha(32) - - # Send the command to the session's stdin. - # NOTE: if the session echoes input we don't need to echo the token twice. - shell_write(cmd + ";echo #{token}\n") - shell_read_until_token(token) - end - - # - # Explicitly run a single command and return the output. - # This version uses a marker to denote the end of data (instead of a timeout). - # - def shell_command_token_win32(cmd) - # read any pending data - buf = shell_read(-1, 0.01) - token = ::Rex::Text.rand_text_alpha(32) - - # Send the command to the session's stdin. - # NOTE: if the session echoes input we don't need to echo the token twice. - shell_write(cmd + "&echo #{token}\n") - shell_read_until_token(token, 1) - end - - # # Read from the command shell. # diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index 4659a2b573..ef1fa103fd 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -25,6 +25,11 @@ class Meterpreter < Rex::Post::Meterpreter::Client include Msf::Session::Interactive include Msf::Session::Comm + # + # This interface supports interacting with a single command shell. + # + include Msf::Session::Provider::SingleCommandShell + include Msf::Session::Scriptable # Override for server implementations that can't do ssl @@ -76,6 +81,79 @@ class Meterpreter < Rex::Post::Meterpreter::Client self.class.type end + def shell_init + return true if @shell + + # COMSPEC is special-cased on all meterpreters to return a viable + # shell. + sh = fs.file.expand_path("%COMSPEC%") + @shell = sys.process.execute(sh, nil, { "Hidden" => true, "Channelized" => true }) + + end + + # + # Read from the command shell. + # + def shell_read(length=nil, timeout=1) + shell_init + + length = nil if length < 0 + begin + rv = nil + # Meterpreter doesn't offer a way to timeout on the victim side, so + # we have to do it here. I'm concerned that this will cause loss + # of data. + Timeout.timeout(timeout) { + rv = @shell.channel.read(length) + } + framework.events.on_session_output(self, rv) if rv + return rv + rescue ::Timeout::Error + return nil + rescue ::Exception => e + shell_close + raise e + end + end + + # + # Write to the command shell. + # + def shell_write(buf) + shell_init + + begin + framework.events.on_session_command(self, buf.strip) + len = @shell.channel.write(buf + "\r\n") + rescue ::Exception => e + shell_close + raise e + end + end + + def shell_close + @shell.close + @shell = nil + end + + def shell_command(cmd) + # Send the shell channel's stdin. + shell_write(cmd + "\n") + + timeout = 5 + etime = ::Time.now.to_f + timeout + buff = "" + + # Keep reading data until no more data is available or the timeout is + # reached. + while (::Time.now.to_f < etime) + res = shell_read(-1, 0.1) + buff << res if res + end + + buff + end + # # Called by PacketDispatcher to resolve error codes to names. # This is the default version (return the number itself) @@ -168,7 +246,7 @@ class Meterpreter < Rex::Post::Meterpreter::Client end # - # Explicitly runs a command. + # Explicitly runs a command in the meterpreter console. # def run_cmd(cmd) console.run_single(cmd) diff --git a/lib/msf/core/session/provider/single_command_shell.rb b/lib/msf/core/session/provider/single_command_shell.rb index ddcdf6afc3..54a0d9ffde 100644 --- a/lib/msf/core/session/provider/single_command_shell.rb +++ b/lib/msf/core/session/provider/single_command_shell.rb @@ -38,6 +38,83 @@ module SingleCommandShell raise NotImplementedError end + # + # Read data until we find the token + # + def shell_read_until_token(token, wanted_idx = 0, timeout = 10) + if (wanted_idx == 0) + parts_needed = 2 + else + parts_needed = 1 + (wanted_idx * 2) + end + + # Read until we get the data between two tokens or absolute timeout. + begin + ::Timeout.timeout(timeout) do + buf = '' + idx = nil + loop do + if (tmp = shell_read(-1, 2)) + buf << tmp + + # see if we have the wanted idx + parts = buf.split(token, -1) + if (parts.length == parts_needed) + # cause another prompt to appear (just in case) + shell_write("\n") + return parts[wanted_idx] + end + end + end + end + rescue + # nothing, just continue + end + + # failed to get any data or find the token! + nil + end + + def shell_command_token(cmd) + if platform =~ /win/ + output = shell_command_token_win32(cmd) + else + output = shell_command_token_unix(cmd) + end + output + end + + # + # Explicitly run a single command and return the output. + # This version uses a marker to denote the end of data (instead of a timeout). + # + def shell_command_token_unix(cmd) + # read any pending data + buf = shell_read(-1, 0.01) + token = ::Rex::Text.rand_text_alpha(32) + + # Send the command to the session's stdin. + # NOTE: if the session echoes input we don't need to echo the token twice. + shell_write(cmd + ";echo #{token}\n") + shell_read_until_token(token) + end + + # + # Explicitly run a single command and return the output. + # This version uses a marker to denote the end of data (instead of a timeout). + # + def shell_command_token_win32(cmd) + # read any pending data + buf = shell_read(-1, 0.01) + token = ::Rex::Text.rand_text_alpha(32) + + # Send the command to the session's stdin. + # NOTE: if the session echoes input we don't need to echo the token twice. + shell_write(cmd + "&echo #{token}\n") + shell_read_until_token(token, 1) + end + + end end