Land #6077, initial python meterpreter module support
commit
cb403b8a36
|
@ -0,0 +1,114 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/meterpreter/extensions/python/tlv'
|
||||
require 'set'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Python
|
||||
|
||||
###
|
||||
#
|
||||
# Python extension - gives remote python scripting capabilities on the target.
|
||||
#
|
||||
###
|
||||
|
||||
class Python < Extension
|
||||
|
||||
PY_CODE_TYPE_STRING = 0
|
||||
PY_CODE_TYPE_PY = 1
|
||||
PY_CODE_TYPE_PYC = 2
|
||||
|
||||
PY_CODE_FILE_TYPES = [ '.py', '.pyc' ]
|
||||
|
||||
PY_CODE_FILE_TYPE_MAP = {
|
||||
'.py' => PY_CODE_TYPE_PY,
|
||||
'.pyc' => PY_CODE_TYPE_PYC
|
||||
}
|
||||
|
||||
#
|
||||
# Typical extension initialization routine.
|
||||
#
|
||||
# @param client (see Extension#initialize)
|
||||
def initialize(client)
|
||||
super(client, 'python')
|
||||
|
||||
client.register_extension_aliases(
|
||||
[
|
||||
{
|
||||
'name' => 'python',
|
||||
'ext' => self
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
def reset
|
||||
request = Packet.create_request('python_reset')
|
||||
client.send_request(request)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def import(file, mod_name, result_var)
|
||||
unless ::File.file?(file)
|
||||
raise ArgumentError, "File not found: #{file}"
|
||||
end
|
||||
|
||||
ext = ::File.extname(file).downcase
|
||||
unless PY_CODE_FILE_TYPES.include?(ext)
|
||||
raise ArgumentError, "File not a valid type: #{file}"
|
||||
end
|
||||
|
||||
code = ::File.read(file)
|
||||
|
||||
request = Packet.create_request('python_execute')
|
||||
request.add_tlv(TLV_TYPE_PYTHON_CODE, code)
|
||||
request.add_tlv(TLV_TYPE_PYTHON_CODE_LEN, code.length)
|
||||
request.add_tlv(TLV_TYPE_PYTHON_CODE_TYPE, PY_CODE_FILE_TYPE_MAP[ext])
|
||||
request.add_tlv(TLV_TYPE_PYTHON_NAME, mod_name) if mod_name
|
||||
request.add_tlv(TLV_TYPE_PYTHON_RESULT_VAR, result_var) if result_var
|
||||
|
||||
run_exec_request(request)
|
||||
end
|
||||
|
||||
#
|
||||
# Dump the LSA secrets from the target machine.
|
||||
#
|
||||
# @return [Hash<Symbol,Object>]
|
||||
def execute_string(code, result_var)
|
||||
request = Packet.create_request('python_execute')
|
||||
request.add_tlv(TLV_TYPE_PYTHON_CODE, code)
|
||||
request.add_tlv(TLV_TYPE_PYTHON_CODE_TYPE, PY_CODE_TYPE_STRING)
|
||||
request.add_tlv(TLV_TYPE_PYTHON_RESULT_VAR, result_var) if result_var
|
||||
|
||||
run_exec_request(request)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run_exec_request(request)
|
||||
response = client.send_request(request)
|
||||
|
||||
result = {
|
||||
result: response.get_tlv_value(TLV_TYPE_PYTHON_RESULT),
|
||||
stdout: "",
|
||||
stderr: ""
|
||||
}
|
||||
|
||||
response.each(TLV_TYPE_PYTHON_STDOUT) do |o|
|
||||
result[:stdout] << o.value
|
||||
end
|
||||
|
||||
response.each(TLV_TYPE_PYTHON_STDERR) do |e|
|
||||
result[:stderr] << e.value
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end; end; end; end; end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Python
|
||||
|
||||
TLV_TYPE_PYTHON_STDOUT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 1)
|
||||
TLV_TYPE_PYTHON_STDERR = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 2)
|
||||
TLV_TYPE_PYTHON_CODE = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 3)
|
||||
TLV_TYPE_PYTHON_CODE_LEN = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 4)
|
||||
TLV_TYPE_PYTHON_CODE_TYPE = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 5)
|
||||
TLV_TYPE_PYTHON_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 6)
|
||||
TLV_TYPE_PYTHON_RESULT_VAR = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 7)
|
||||
TLV_TYPE_PYTHON_RESULT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 8)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,187 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/post/meterpreter'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Ui
|
||||
|
||||
###
|
||||
#
|
||||
# Python extension - interact with a python interpreter
|
||||
#
|
||||
###
|
||||
class Console::CommandDispatcher::Python
|
||||
|
||||
Klass = Console::CommandDispatcher::Python
|
||||
|
||||
include Console::CommandDispatcher
|
||||
|
||||
#
|
||||
# Name for this dispatcher
|
||||
#
|
||||
def name
|
||||
'Python'
|
||||
end
|
||||
|
||||
#
|
||||
# List of supported commands.
|
||||
#
|
||||
def commands
|
||||
{
|
||||
'python_reset' => 'Resets/restarts the Python interpreter',
|
||||
'python_execute' => 'Execute a python command string',
|
||||
'python_import' => 'Import/run a python file or module'
|
||||
}
|
||||
end
|
||||
|
||||
def cmd_python_reset(*args)
|
||||
client.python.reset
|
||||
print_good('Python interpreter successfully reset')
|
||||
end
|
||||
|
||||
@@python_import_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [false, 'Help banner'],
|
||||
'-f' => [true, 'Path to the file (.py, .pyc), or module directory to import'],
|
||||
'-n' => [true, 'Name of the module (optional, for single files only)'],
|
||||
'-r' => [true, 'Name of the variable containing the result (optional, single files only)']
|
||||
)
|
||||
|
||||
def python_import_usage
|
||||
print_line('Usage: python_import <-f file path> [-n mod name] [-r result var name]')
|
||||
print_line
|
||||
print_line('Loads a python code file or module from disk into memory on the target.')
|
||||
print_line('The module loader requires a path to a folder that contains the module,')
|
||||
print_line('and the folder name will be used as the module name. Only .py files will')
|
||||
print_line('work with modules.')
|
||||
print_line(@@python_import_opts.usage)
|
||||
end
|
||||
|
||||
#
|
||||
# Import/run a python file
|
||||
#
|
||||
def cmd_python_import(*args)
|
||||
if args.length == 0 || args.include?('-h')
|
||||
python_import_usage
|
||||
return false
|
||||
end
|
||||
|
||||
result_var = nil
|
||||
source = nil
|
||||
mod_name = nil
|
||||
|
||||
@@python_import_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when '-f'
|
||||
source = val
|
||||
when '-n'
|
||||
mod_name = val
|
||||
when '-r'
|
||||
result_var = val
|
||||
end
|
||||
}
|
||||
|
||||
unless source
|
||||
print_error("The -f parameter must be specified")
|
||||
return false
|
||||
end
|
||||
|
||||
if ::File.directory?(source)
|
||||
files = ::Find.find(source).select { |p| /.*\.py$/ =~ p }
|
||||
if files.length == 0
|
||||
fail_with("No .py files found in #{source}")
|
||||
end
|
||||
|
||||
base_name = ::File.basename(source)
|
||||
unless source.end_with?('/')
|
||||
source << '/'
|
||||
end
|
||||
|
||||
print_status("Importing #{source} with base module name #{base_name} ...")
|
||||
|
||||
files.each do |file|
|
||||
rel_path = file[source.length, file.length - source.length]
|
||||
parts = rel_path.split('/')
|
||||
|
||||
mod_parts = [base_name] + parts[0, parts.length - 1]
|
||||
|
||||
if parts[-1] != '__init__.py'
|
||||
mod_parts << ::File.basename(parts[-1], '.*')
|
||||
end
|
||||
|
||||
mod_name = mod_parts.join('.')
|
||||
print_status("Importing #{file} as #{mod_name} ...")
|
||||
result = client.python.import(file, mod_name, nil)
|
||||
handle_exec_result(result, nil)
|
||||
end
|
||||
else
|
||||
print_status("Importing #{source} ...")
|
||||
result = client.python.import(source, mod_name, result_var)
|
||||
handle_exec_result(result, result_var)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@python_execute_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [false, 'Help banner'],
|
||||
'-r' => [true, 'Name of the variable containing the result (optional)']
|
||||
)
|
||||
|
||||
def python_execute_usage
|
||||
print_line('Usage: python_execute <python code> [-r result var name]')
|
||||
print_line
|
||||
print_line('Runs the given python string on the target. If a result is required,')
|
||||
print_line('it should be stored in a python variable, and that variable should')
|
||||
print_line('passed using the -r parameter.')
|
||||
print_line(@@python_execute_opts.usage)
|
||||
end
|
||||
|
||||
#
|
||||
# Execute a simple python command string
|
||||
#
|
||||
def cmd_python_execute(*args)
|
||||
if args.length == 0 || args.include?('-h')
|
||||
python_execute_usage
|
||||
return false
|
||||
end
|
||||
|
||||
code = args.shift
|
||||
result_var = nil
|
||||
|
||||
@@python_execute_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when '-r'
|
||||
result_var = val
|
||||
end
|
||||
}
|
||||
|
||||
result = client.python.execute_string(code, result_var)
|
||||
|
||||
handle_exec_result(result, result_var)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_exec_result(result, result_var)
|
||||
if result[:result]
|
||||
print_good("#{result_var} = #{result[:result]}")
|
||||
elsif result[:stdout].length == 0 and result[:stderr].length == 0
|
||||
print_good("Command executed without returning a result")
|
||||
end
|
||||
|
||||
if result[:stdout].length > 0
|
||||
print_good("Content written to stdout:\n#{result[:stdout]}")
|
||||
end
|
||||
|
||||
if result[:stderr].length > 0
|
||||
print_error("Content written to stderr:\n#{result[:stderr]}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue