Skip to content

Commit

Permalink
Land #15109, Better handling for incompatible Meterpreter extensions …
Browse files Browse the repository at this point in the history
…and commands (Round 2)
  • Loading branch information
gwillcox-r7 committed Jun 16, 2021
2 parents 41fca09 + 626bbeb commit b91c829
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 64 deletions.
19 changes: 11 additions & 8 deletions lib/rex/post/meterpreter/client_core.rb
Expand Up @@ -309,15 +309,17 @@ def load_library(opts)

#
# Loads a meterpreter extension on the remote server instance and
# initializes the client-side extension handlers
# initializes the client-side extension handlers.
#
# Module
# The module that should be loaded
# @param [String] mod The extension that should be loaded.
# @param [Hash] opts The options with which to load the extension.
# @option opts [String] LoadFromDisk Indicates that the library should be
# loaded from disk, not from memory on the remote machine.
#
# LoadFromDisk
# Indicates that the library should be loaded from disk, not from
# memory on the remote machine
# @raise [RuntimeError] An exception is raised if the extension could not be
# loaded.
#
# @return [true] This always returns true or raises an exception.
def use(mod, opts = { })
if mod.nil?
raise RuntimeError, "No modules were specified", caller
Expand Down Expand Up @@ -364,10 +366,11 @@ def use(mod, opts = { })
end

if path.nil? and image.nil?
error = Rex::Post::Meterpreter::ExtensionLoadError.new(name: mod.downcase)
if Rex::Post::Meterpreter::ExtensionMapper.get_extension_names.include?(mod.downcase)
raise RuntimeError, "The \"#{mod.downcase}\" extension is not supported by this Meterpreter type (#{client.session_type})", caller
raise error, "The \"#{mod.downcase}\" extension is not supported by this Meterpreter type (#{client.session_type})", caller
else
raise RuntimeError, "No module of the name #{modnameprovided} found", caller
raise error, "No module of the name #{modnameprovided} found", caller
end
end

Expand Down
15 changes: 15 additions & 0 deletions lib/rex/post/meterpreter/extension.rb
Expand Up @@ -4,6 +4,21 @@ module Rex
module Post
module Meterpreter

#
# An error that is raised when a particular Meterpreter extension can not be
# loaded for any reason.
#
# @attr_reader [String] name The name of the extension that could not be loaded.
class ExtensionLoadError < RuntimeError
attr_reader :name

# @param [String] name The name of the extension that could not be loaded.
def initialize(name:)
@name = name
super
end
end

###
#
# Base class for all extensions that holds a reference to the
Expand Down
18 changes: 0 additions & 18 deletions lib/rex/post/meterpreter/extensions/mimikatz/command_ids.rb

This file was deleted.

3 changes: 0 additions & 3 deletions lib/rex/post/meterpreter/packet.rb
Expand Up @@ -150,7 +150,6 @@ def self.generate_command_id_map_c
powershell
lanattacks
peinjector
mimikatz
})

command_ids = id_map.map {|k, v| "#define COMMAND_ID_#{k.upcase} #{v}"}
Expand Down Expand Up @@ -234,7 +233,6 @@ def self.generate_command_id_map_python_extension
powershell
lanattacks
peinjector
mimikatz
})
command_ids = id_map.map {|k, v| "COMMAND_ID_#{k.upcase} = #{v}"}
%Q^
Expand Down Expand Up @@ -263,7 +261,6 @@ def self.generate_command_id_map_csharp
powershell
lanattacks
peinjector
mimikatz
})
command_ids = id_map.map {|k, v| "#{k.split('_').map(&:capitalize).join} = #{v},"}
%Q^
Expand Down
118 changes: 84 additions & 34 deletions lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb
Expand Up @@ -1320,9 +1320,45 @@ def cmd_load(*args)
if (client.core.use(modulenameprovided) == true)
add_extension_client(md)
end
rescue
rescue => ex
print_line
log_error("Failed to load extension: #{$!}")
log_error("Failed to load extension: #{ex.message}")
if ex.kind_of?(ExtensionLoadError) && ex.name
# MetasploitPayloads and MetasploitPayloads::Mettle do things completely differently, build an array of
# suggestion keys (binary_suffixes and Mettle build-tuples)
suggestion_keys = MetasploitPayloads.list_meterpreter_extension_suffixes(ex.name) + MetasploitPayloads::Mettle.available_platforms(ex.name)
suggestion_map = {
# Extension Suffixes
'jar' => 'java',
'php' => 'php',
'py' => 'python',
'x64.dll' => 'windows/x64',
'x86.dll' => 'windows',
# Mettle Platforms
'aarch64-iphone-darwin' => 'apple_ios/aarch64',
'aarch64-linux-musl' => 'linux/aarch64',
'arm-iphone-darwin' => 'apple_ios/armle',
'armv5b-linux-musleabi' => 'linux/armbe',
'armv5l-linux-musleabi' => 'linux/armle',
'i486-linux-musl' => 'linux/x86',
'mips64-linux-muslsf' => 'linux/mips64',
'mipsel-linux-muslsf' => 'linux/mipsle',
'mips-linux-muslsf' => 'linux/mipsbe',
'powerpc64le-linux-musl' => 'linux/ppc64le',
'powerpc-e500v2-linux-musl' => 'linux/ppce500v2',
'powerpc-linux-muslsf' => 'linux/ppc',
's390x-linux-musl' => 'linux/zarch',
'x86_64-apple-darwin' => 'osx/x64',
'x86_64-linux-musl' => 'linux/x64',
}
suggestions = suggestion_map.select { |k,_v| suggestion_keys.include?(k) }.values
unless suggestions.empty?
log_error("The \"#{ex.name}\" extension is supported by the following Meterpreter payloads:")
suggestions.each do |suggestion|
log_error(" - #{suggestion}/meterpreter*")
end
end
end

next
end
Expand Down Expand Up @@ -1762,6 +1798,27 @@ def self.client_extension_search_paths
@@client_extension_search_paths
end

def unknown_command(cmd, line)
status = super

if status.nil?
# Check to see if we can find this command in another extension. This relies on the core extension being the last
# in the dispatcher stack which it should be since it's the first loaded.
Rex::Post::Meterpreter::ExtensionMapper.get_extension_names.each do |ext_name|
next if extensions.include?(ext_name)
ext_klass = get_extension_client_class(ext_name)
next if ext_klass.nil?

if ext_klass.has_command?(cmd)
print_error("The \"#{cmd}\" command requires the \"#{ext_name}\" extension to be loaded (run: `load #{ext_name}`)")
return :handled
end
end
end

status
end

protected

attr_accessor :extensions # :nodoc:
Expand All @@ -1773,39 +1830,9 @@ def self.client_extension_search_paths
# Loads the client extension specified in mod
#
def add_extension_client(mod)
loaded = false
klass = nil
self.class.client_extension_search_paths.each do |path|
path = ::File.join(path, "#{mod}.rb")
klass = CommDispatcher.check_hash(path)
if (klass == nil)
old = CommDispatcher.constants
next unless ::File.exist? path

if (require(path))
new = CommDispatcher.constants
diff = new - old

next if (diff.empty?)

klass = CommDispatcher.const_get(diff[0])

CommDispatcher.set_hash(path, klass)
loaded = true
break
else
print_error("Failed to load client script file: #{path}")
return false
end
klass = get_extension_client_class(mod)

else
# the klass is already loaded, from a previous invocation
loaded = true
break
end
end

unless loaded
if klass.nil?
print_error("Failed to load client portion of #{mod}.")
return false
end
Expand All @@ -1817,6 +1844,29 @@ def add_extension_client(mod)
self.extensions << mod
end

def get_extension_client_class(mod)
self.class.client_extension_search_paths.each do |path|
path = ::File.join(path, "#{mod}.rb")
klass = CommDispatcher.check_hash(path)
return klass unless klass.nil?

old = CommDispatcher.constants
next unless ::File.exist? path

return nil unless require(path)

new = CommDispatcher.constants
diff = new - old

next if (diff.empty?)

klass = CommDispatcher.const_get(diff[0])

CommDispatcher.set_hash(path, klass)
return klass
end
end

def tab_complete_modules(str, words)
tabs = []
client.framework.modules.post.map do |name,klass|
Expand Down
Expand Up @@ -32,6 +32,10 @@ class Console::CommandDispatcher::Extapi

include Console::CommandDispatcher

def self.has_command?(name)
Dispatchers.any? { |klass| klass.has_command?(name) }
end

#
# Initializes an instance of the extended API command interaction.
#
Expand Down
Expand Up @@ -26,6 +26,10 @@ class Console::CommandDispatcher::Lanattacks

include Console::CommandDispatcher

def self.has_command?(name)
Dispatchers.any? { |klass| klass.has_command?(name) }
end

#
# Initializes an instance of the lanattacks command interaction.
#
Expand Down
Expand Up @@ -28,6 +28,10 @@ class Console::CommandDispatcher::Priv

include Console::CommandDispatcher

def self.has_command?(name)
Dispatchers.any? { |klass| klass.has_command?(name) }
end

#
# Initializes an instance of the priv command interaction.
#
Expand Down
Expand Up @@ -37,6 +37,10 @@ class Console::CommandDispatcher::Stdapi

include Console::CommandDispatcher

def self.has_command?(name)
Dispatchers.any? { |klass| klass.has_command?(name) }
end

#
# Initializes an instance of the stdapi command interaction.
#
Expand Down
25 changes: 24 additions & 1 deletion lib/rex/ui/text/dispatcher_shell.rb
Expand Up @@ -28,6 +28,29 @@ module DispatcherShell
###
module CommandDispatcher

module ClassMethods
#
# Check whether or not the command dispatcher is capable of handling the
# specified command. The command may still be disabled through some means
# at runtime.
#
# @param [String] name The name of the command to check.
# @return [Boolean] true if the dispatcher can handle the command.
def has_command?(name)
self.method_defined?("cmd_#{name}")
end

def included(base)
# Propagate the included hook
CommandDispatcher.included(base)
end
end

def self.included(base)
# Install class methods so they are inheritable
base.extend(ClassMethods)
end

#
# Initializes the command dispatcher mixin.
#
Expand Down Expand Up @@ -539,7 +562,7 @@ def run_command(dispatcher, method, arguments)
# If the command is unknown...
#
def unknown_command(method, line)
print_error("Unknown command: #{method}.")
print_error("Unknown command: #{method}")
end

#
Expand Down

0 comments on commit b91c829

Please sign in to comment.