Land #10448, Implementation of CTRL+C to send SIGINT signal

4.x
asoto-r7 2018-08-20 18:14:29 -05:00 committed by Metasploit
parent 8c29a3b5da
commit 8ce1329e74
No known key found for this signature in database
GPG Key ID: CDFB5FA52007B954
9 changed files with 296 additions and 187 deletions

View File

@ -3,7 +3,7 @@ require 'msf/base'
require 'msf/base/sessions/scriptable'
require 'shellwords'
require 'rex/text/table'
require "base64"
module Msf
module Sessions
@ -29,6 +29,7 @@ class CommandShell
include Msf::Session::Scriptable
include Rex::Ui::Text::Resource
##
# :category: Msf::Session::Scriptable implementors
@ -68,6 +69,14 @@ class CommandShell
"Command shell"
end
##
# :category: Msf::Session::Provider::SingleCommandShell implementors
#
# The shell will have been initialized by default.
#
def shell_init
return true
end
#
# List of supported commands.
@ -77,35 +86,11 @@ class CommandShell
'help' => 'Help menu',
'background' => 'Backgrounds the current shell session',
'sessions' => 'Quickly switch to another session',
'resource' => 'Run the commands stored in a file',
'shell' => 'Spawn an interactive shell',
}
end
#
# Parse a line into an array of arguments.
#
def parse_line(line)
line.split(' ')
end
#
# Explicitly runs a command.
#
def run_cmd(cmd)
# Do nil check for cmd (CTRL+D will cause nil error)
return unless cmd
arguments = parse_line(cmd)
method = arguments.shift
# Built-in command
if commands.key?(method)
return run_command(method, arguments)
end
# User input is not a built-in command, write to socket directly
shell_write(cmd)
end
def cmd_help(*args)
columns = ['Command', 'Description']
tbl = Rex::Text::Table.new(
@ -122,7 +107,7 @@ class CommandShell
print(tbl.to_s)
end
def cmd_background_help()
def cmd_background_help
print_line "Usage: background"
print_line
print_line "Stop interacting with this session and return to the parent prompt"
@ -142,7 +127,7 @@ class CommandShell
end
end
def cmd_sessions_help()
def cmd_sessions_help
print_line('Usage: sessions <id>')
print_line
print_line('Interact with a different session Id.')
@ -179,30 +164,154 @@ class CommandShell
end
end
def cmd_resource(*args)
if args.empty?
cmd_resource_help
return false
end
args.each do |res|
good_res = nil
if res == '-'
good_res = res
elsif ::File.exist?(res)
good_res = res
elsif
# let's check to see if it's in the scripts/resource dir (like when tab completed)
[
::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter'
].each do |dir|
res_path = ::File::join(dir, res)
if ::File.exist?(res_path)
good_res = res_path
break
end
end
end
if good_res
load_resource(good_res)
else
print_error("#{res} is not a valid resource file")
next
end
end
end
def cmd_resource_help
print_line "Usage: resource path1 [path2 ...]"
print_line
print_line "Run the commands stored in the supplied files. (- for stdin, press CTRL+D to end input from stdin)"
print_line "Resource files may also contain ERB or Ruby code between <ruby></ruby> tags."
print_line
end
def cmd_shell_help()
print_line('Usage: shell')
print_line
print_line('Pop up an interactive shell via multi methods.')
print_line('An interactive shell means that you can use several useful commands like `passwd`, `su [username]`')
print_line('There are three implementation of it: ')
print_line('\t1. using python `pty` module (default choice)')
print_line('\t2. using `socat` command')
print_line('\t3. using `script` command')
print_line('\t4. upload a pty program via reverse shell')
print_line
end
def cmd_shell(*args)
if args.length == 1 && (args[1] == '-h' || args[1] == 'help')
# One arg, and args[1] => '-h' '-H' 'help'
return cmd_sessions_help
end
# Why `/bin/sh` not `/bin/bash`, some machine may not have `/bin/bash` installed, just in case.
# 1. Using python
# 1.1 Check Python installed or not
# We do not need to care about the python version
# Beacuse python2 and python3 have the same payload of spawn a shell
python_path = binary_exists("python")
if python_path != nil
# Payload: import pty;pty.spawn('/bin/sh')
# Base64 encoded payload: aW1wb3J0IHB0eTtwdHkuc3Bhd24oJy9iaW4vc2gnKQ==
print_status("Using `python` to pop up an interactive shell")
shell_command("#{python_path} -c 'exec(\"aW1wb3J0IHB0eTtwdHkuc3Bhd24oJy9iaW4vc2gnKQ==\".decode(\"base64\"))'")
return
end
# 2. Using script
script_path = binary_exists("script")
if script_path != nil
print_status("Using `script` to pop up an interactive shell")
# Payload: script /dev/null
# Using /dev/null to make sure there is no log file on the target machine
# Prevent being detected by the admin or antivirus softwares
shell_command("#{script_path} /dev/null")
return
end
# 3. Using socat
socat_path = binary_exists("socat")
if socat_path != nil
# Payload: socat - exec:'bash -li',pty,stderr,setsid,sigint,sane
print_status("Using `socat` to pop up an interactive shell")
shell_command("#{socat_path} - exec:'/bin/sh -li',pty,stderr,setsid,sigint,sane")
return
end
# 4. Using pty program
# 4.1 Detect arch and destribution
# 4.2 Real time compiling
# 4.3 Upload binary
# 4.4 Change mode of binary
# 4.5 Execute binary
print_error("Can not pop up an interactive shell")
end
#
# Check if there is a binary in PATH env
#
def binary_exists(binary)
print_status("Trying to find binary(#{binary}) on target machine")
binary_path = shell_command_token("which #{binary}").strip
p binary_path
if binary_path.eql?("#{binary} not found")
print_error(binary_path)
return nil
else
print_status("Found #{binary} at #{binary_path}")
return binary_path
end
end
#
# Explicitly runs a single line command.
#
def run_single(cmd)
# Do nil check for cmd (CTRL+D will cause nil error)
return unless cmd
arguments = cmd.split(' ')
method = arguments.shift
# Built-in command
if commands.key?(method)
return run_builtin_cmd(method, arguments)
end
# User input is not a built-in command, write to socket directly
shell_write(cmd)
end
#
# Run built-in command
#
def run_command(method, arguments)
def run_builtin_cmd(method, arguments)
# Dynamic function call
self.send('cmd_' + method, *arguments)
end
#
# Calls the class method.
#
def type
self.class.type
end
##
# :category: Msf::Session::Provider::SingleCommandShell implementors
#
# The shell will have been initialized by default.
#
def shell_init
return true
end
##
# :category: Msf::Session::Provider::SingleCommandShell implementors
#
@ -250,7 +359,7 @@ class CommandShell
# Writes to the command shell.
#
def shell_write(buf)
return if not buf
return unless buf
begin
framework.events.on_session_command(self, buf.strip)
@ -351,13 +460,13 @@ protected
fds = [rstream.fd, user_input.fd]
while self.interacting
sd = Rex::ThreadSafe.select(fds, nil, fds, 0.5)
next if not sd
next unless sd
if sd[0].include? rstream.fd
user_output.print(shell_read)
end
if sd[0].include? user_input.fd
shell_write(user_input.gets)
run_single(user_input.gets)
end
Thread.pass
end
@ -385,4 +494,4 @@ class CommandShellUnix < CommandShell
end
end
end
end

View File

@ -109,10 +109,29 @@ protected
#
def _interrupt
begin
user_want_abort?
intent = user_want_abort?
# Judge the user wants to abort the reverse shell session
# Or just want to abort the process running on the target machine
# If the latter, just send ASCII Control Character \u0003 (End of Text) to the socket fd
# The character will be handled by the line dicipline program of the pseudo-terminal on target machine
# It will send the SEGINT singal to the foreground process
if !intent
# TODO: Check the shell is interactive or not
# If the current shell is not interactive, the ASCII Control Character will not work
self.rstream.write("\u0003")
return
end
rescue Interrupt
# The user hit ctrl-c while we were handling a ctrl-c. Ignore
end
p ""
end
def _usr1
# A simple signal to exit vim in reverse shell
# Just for fun
# Make sure you have already executed `shell` meta-shell command to pop up an interactive shell
self.rstream.write("\x1B\x1B\x1B:q!\r")
end
#
@ -137,7 +156,7 @@ protected
# Checks to see if the user wants to abort.
#
def user_want_abort?
prompt_yesno("Abort session #{name}?")
prompt_yesno("Abort session #{name}? If not, the foreground process in the session will be killed")
end
end

View File

@ -46,6 +46,8 @@ class Driver < Msf::Ui::Driver
#
include Rex::Ui::Text::DispatcherShell
include Rex::Ui::Text::Resource
#
# Initializes a console driver instance with the supplied prompt string and
# prompt character. The optional hash can take extra values that will
@ -315,70 +317,6 @@ class Driver < Msf::Ui::Driver
end
end
# Processes a resource script file for the console.
#
# @param path [String] Path to a resource file to run
# @return [void]
def load_resource(path)
if path == '-'
resource_file = $stdin.read
path = 'stdin'
elsif ::File.exist?(path)
resource_file = ::File.read(path)
else
print_error("Cannot find resource script: #{path}")
return
end
# Process ERB directives first
print_status "Processing #{path} for ERB directives."
erb = ERB.new(resource_file)
processed_resource = erb.result(binding)
lines = processed_resource.each_line.to_a
bindings = {}
while lines.length > 0
line = lines.shift
break if not line
line.strip!
next if line.length == 0
next if line =~ /^#/
# Pretty soon, this is going to need an XML parser :)
# TODO: case matters for the tag and for binding names
if line =~ /<ruby/
if line =~ /\s+binding=(?:'(\w+)'|"(\w+)")(>|\s+)/
bin = ($~[1] || $~[2])
bindings[bin] = binding unless bindings.has_key? bin
bin = bindings[bin]
else
bin = binding
end
buff = ''
while lines.length > 0
line = lines.shift
break if not line
break if line =~ /<\/ruby>/
buff << line
end
if ! buff.empty?
print_status("resource (#{path})> Ruby Code (#{buff.length} bytes)")
begin
eval(buff, bin)
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("resource (#{path})> Ruby Error: #{e.class} #{e} #{e.backtrace}")
end
end
else
print_line("resource (#{path})> #{line}")
run_single(line)
end
end
end
#
# Saves the recent history to the specified file
#
@ -662,4 +600,4 @@ end
end
end
end
end

View File

@ -37,4 +37,4 @@ protected
end
end
end
end

View File

@ -1580,7 +1580,7 @@ class Console::CommandDispatcher::Core
def cmd_resource_help
print_line "Usage: resource path1 [path2 ...]"
print_line
print_line "Run the commands stored in the supplied files (- for stdin)."
print_line "Run the commands stored in the supplied files. (- for stdin, press CTRL+D to end input from stdin)"
print_line "Resource files may also contain ERB or Ruby code between <ruby></ruby> tags."
print_line
end

View File

@ -11,6 +11,7 @@ require 'rex/ui/output'
# Text-based user interfaces
require 'rex/ui/text/input'
require 'rex/ui/text/shell'
require 'rex/ui/text/resource'
require 'rex/ui/text/dispatcher_shell'
require 'rex/ui/text/irb_shell'
require 'rex/ui/text/bidirectional_pipe'

View File

@ -42,6 +42,8 @@ module Interactive
# Handle suspend notifications
handle_suspend
handle_usr1
# As long as we're interacting...
while (self.interacting == true)
@ -128,6 +130,7 @@ protected
# The original suspend proc.
#
attr_accessor :orig_suspend
attr_accessor :orig_usr1
#
# Stub method that is meant to handler interaction
@ -230,6 +233,7 @@ protected
end
end
#
# Restores the previously installed signal handler for suspend
# notifications.
@ -246,6 +250,30 @@ protected
end
end
def handle_usr1
if orig_usr1.nil?
begin
self.orig_usr1 = Signal.trap("USR1") do
Thread.new { _usr1 }.join
end
rescue
end
end
end
def restore_usr1
begin
if orig_usr1
Signal.trap("USR1", orig_usr1)
else
Signal.trap("USR1", "DEFAULT")
end
self.orig_usr1 = nil
rescue
end
end
#
# Prompt the user for input if possible.
# XXX: This is not thread-safe on Windows

View File

@ -20,6 +20,8 @@ module Text
###
module DispatcherShell
include Resource
###
#
# Empty template base class for command dispatchers.
@ -417,73 +419,6 @@ module DispatcherShell
return items
end
# Processes a resource script file for the console.
#
# @param path [String] Path to a resource file to run
# @return [void]
def load_resource(path)
if path == '-'
resource_file = $stdin.read
path = 'stdin'
elsif ::File.exist?(path)
resource_file = ::File.read(path)
else
print_error("Cannot find resource script: #{path}")
return
end
# Process ERB directives first
print_status "Processing #{path} for ERB directives."
erb = ERB.new(resource_file)
processed_resource = erb.result(binding)
lines = processed_resource.each_line.to_a
bindings = {}
while lines.length > 0
line = lines.shift
break if not line
line.strip!
next if line.length == 0
next if line =~ /^#/
# Pretty soon, this is going to need an XML parser :)
# TODO: case matters for the tag and for binding names
if line =~ /<ruby/
if line =~ /\s+binding=(?:'(\w+)'|"(\w+)")(>|\s+)/
bin = ($~[1] || $~[2])
bindings[bin] = binding unless bindings.has_key? bin
bin = bindings[bin]
else
bin = binding
end
buff = ''
while lines.length > 0
line = lines.shift
break if not line
break if line =~ /<\/ruby>/
buff << line
end
if ! buff.empty?
session = client
framework = client.framework
print_status("resource (#{path})> Ruby Code (#{buff.length} bytes)")
begin
eval(buff, bin)
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("resource (#{path})> Ruby Error: #{e.class} #{e} #{e.backtrace}")
end
end
else
print_line("resource (#{path})> #{line}")
run_single(line)
end
end
end
#
# Run a single command line.
#
@ -657,4 +592,4 @@ end
end
end
end
end

View File

@ -0,0 +1,79 @@
# -*- coding: binary -*-
require 'rex/ui'
require 'erb'
module Rex
module Ui
module Text
module Resource
# Processes a resource script file for the console.
#
# @param path [String] Path to a resource file to run
# @return [void]
def load_resource(path)
if path == '-'
resource_file = $stdin.read
path = 'stdin'
elsif ::File.exist?(path)
resource_file = ::File.read(path)
else
print_error("Cannot find resource script: #{path}")
return
end
# Process ERB directives first
print_status "Processing #{path} for ERB directives."
erb = ERB.new(resource_file)
processed_resource = erb.result(binding)
lines = processed_resource.each_line.to_a
bindings = {}
while lines.length > 0
line = lines.shift
break if not line
line.strip!
next if line.length == 0
next if line =~ /^#/
# Pretty soon, this is going to need an XML parser :)
# TODO: case matters for the tag and for binding names
if line =~ /<ruby/
if line =~ /\s+binding=(?:'(\w+)'|"(\w+)")(>|\s+)/
bin = ($~[1] || $~[2])
bindings[bin] = binding unless bindings.has_key? bin
bin = bindings[bin]
else
bin = binding
end
buff = ''
while lines.length > 0
line = lines.shift
break if not line
break if line =~ /<\/ruby>/
buff << line
end
if ! buff.empty?
print_status("resource (#{path})> Ruby Code (#{buff.length} bytes)")
begin
eval(buff, bin)
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("resource (#{path})> Ruby Error: #{e.class} #{e} #{e.backtrace}")
end
end
else
print_line("resource (#{path})> #{line}")
run_single(line)
end
end
end
end
end
end
end