diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index 68d1ef5de804..c5045cc44fbc 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -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 @@ -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 diff --git a/lib/rex/post/meterpreter/extension.rb b/lib/rex/post/meterpreter/extension.rb index 7db256f222bf..69a748ba01cf 100644 --- a/lib/rex/post/meterpreter/extension.rb +++ b/lib/rex/post/meterpreter/extension.rb @@ -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 diff --git a/lib/rex/post/meterpreter/extensions/mimikatz/command_ids.rb b/lib/rex/post/meterpreter/extensions/mimikatz/command_ids.rb deleted file mode 100644 index 529dc90fecd7..000000000000 --- a/lib/rex/post/meterpreter/extensions/mimikatz/command_ids.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: binary -*- -module Rex -module Post -module Meterpreter -module Extensions -module Mimikatz - -# ID for the extension (needs to be a multiple of 1000) -EXTENSION_ID_MIMIKATZ = 17000 - -# Associated command ids -COMMAND_ID_MIMIKATZ_CUSTOM_COMMAND = EXTENSION_ID_MIMIKATZ + 1 - -end -end -end -end -end diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index b073eb89b1df..0adcdee4b9a9 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -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}"} @@ -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^ @@ -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^ diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index 0d44862a20ca..7f54cd0cea80 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -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 @@ -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: @@ -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 @@ -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| diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb index 42d8278ab4c9..6abc742fb2ab 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb @@ -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. # diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks.rb index f396068a1fe0..eb4e947d51fc 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks.rb @@ -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. # diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/priv.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/priv.rb index 8df69213edbf..ca9c06226ea2 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/priv.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/priv.rb @@ -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. # diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb index d35cfeab20ab..7e2bd9c39afc 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi.rb @@ -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. # diff --git a/lib/rex/ui/text/dispatcher_shell.rb b/lib/rex/ui/text/dispatcher_shell.rb index 7c9b4ec274cf..6414c748178d 100644 --- a/lib/rex/ui/text/dispatcher_shell.rb +++ b/lib/rex/ui/text/dispatcher_shell.rb @@ -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. # @@ -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 #