Land #10448, Implementation of CTRL+C to send SIGINT signal
parent
8c29a3b5da
commit
8ce1329e74
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -37,4 +37,4 @@ protected
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue