diff --git a/.gitignore b/.gitignore
index fa85fdbfbf..5651548b7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,8 @@
.yardoc
# Mac OS X files
.DS_Store
+# simplecov coverage data
+coverage
data/meterpreter/ext_server_pivot.dll
data/meterpreter/ext_server_pivot.x64.dll
doc
diff --git a/Gemfile b/Gemfile
index 825f532450..77d2123dae 100755
--- a/Gemfile
+++ b/Gemfile
@@ -24,4 +24,7 @@ end
group :test do
# testing framework
gem 'rspec'
+ # code coverage for tests
+ # any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
+ gem 'simplecov', '0.5.4', :require => false
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 10894e9139..a1fddbd358 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -45,6 +45,10 @@ GEM
rspec-expectations (2.11.3)
diff-lcs (~> 1.1.3)
rspec-mocks (2.11.3)
+ simplecov (0.5.4)
+ multi_json (~> 1.0.3)
+ simplecov-html (~> 0.5.3)
+ simplecov-html (0.5.3)
slop (3.3.3)
tzinfo (0.3.33)
yard (0.8.2.1)
@@ -60,4 +64,5 @@ DEPENDENCIES
rake
redcarpet
rspec
+ simplecov (= 0.5.4)
yard
diff --git a/README.md b/README.md
index fbf2c426ea..c24db4ad92 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework)
+Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rapid7/metasploit-framework)
==
The Metasploit Framework is released under a BSD-style license. See
COPYING for more details.
diff --git a/lib/msf/core/exploit/realport.rb b/lib/msf/core/exploit/realport.rb
index 72727f9594..1b3657fd24 100644
--- a/lib/msf/core/exploit/realport.rb
+++ b/lib/msf/core/exploit/realport.rb
@@ -133,7 +133,7 @@ module Exploit::Remote::RealPort
banner
end
- def realport_send(port=0, data)
+ def realport_send(port=0, data="")
sock.put( [port].pack("C") + data )
end
diff --git a/lib/msf/core/exploit/winrm.rb b/lib/msf/core/exploit/winrm.rb
index 5e6fc32b53..72b6a1f724 100644
--- a/lib/msf/core/exploit/winrm.rb
+++ b/lib/msf/core/exploit/winrm.rb
@@ -323,6 +323,12 @@ module Exploit::Remote::WinRM
end
end
+ def wmi_namespace
+ return datastore['NAMESPACE'] if datastore['NAMESPACE']
+ return @namespace_override if @namespace_override
+ return "/root/cimv2/"
+ end
+
private
@@ -433,7 +439,7 @@ module Exploit::Remote::WinRM
def winrm_uri_action(type)
case type
when "wql"
- return %q{http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*
+ return %Q{http://schemas.microsoft.com/wbem/wsman/1/wmi#{wmi_namespace}*
http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate}
when "create_shell"
return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
diff --git a/lib/msf/core/module_manager/loading.rb b/lib/msf/core/module_manager/loading.rb
index 6b344537f9..0137542c0e 100644
--- a/lib/msf/core/module_manager/loading.rb
+++ b/lib/msf/core/module_manager/loading.rb
@@ -109,4 +109,4 @@ module Msf::ModuleManager::Loading
count_by_type
end
-end
\ No newline at end of file
+end
diff --git a/lib/msf/core/modules/error.rb b/lib/msf/core/modules/error.rb
new file mode 100644
index 0000000000..aa5c2d5e40
--- /dev/null
+++ b/lib/msf/core/modules/error.rb
@@ -0,0 +1,38 @@
+# Base error class for all error under {Msf::Modules}
+class Msf::Modules::Error < StandardError
+ def initialize(attributes={})
+ @module_path = attributes[:module_path]
+ @module_reference_name = attributes[:module_reference_name]
+
+ message_parts = []
+ message_parts << "Failed to load module"
+
+ if module_reference_name or module_path
+ clause_parts = []
+
+ if module_reference_name
+ clause_parts << module_reference_name
+ end
+
+ if module_path
+ clause_parts << "from #{module_path}"
+ end
+
+ clause = clause_parts.join(' ')
+ message_parts << "(#{clause})"
+ end
+
+ causal_message = attributes[:causal_message]
+
+ if causal_message
+ message_parts << "due to #{causal_message}"
+ end
+
+ message = message_parts.join(' ')
+
+ super(message)
+ end
+
+ attr_reader :module_reference_name
+ attr_reader :module_path
+end
\ No newline at end of file
diff --git a/lib/msf/core/modules/loader/base.rb b/lib/msf/core/modules/loader/base.rb
index a7c304b6b0..651e39e412 100644
--- a/lib/msf/core/modules/loader/base.rb
+++ b/lib/msf/core/modules/loader/base.rb
@@ -3,6 +3,7 @@
#
require 'msf/core/modules/loader'
require 'msf/core/modules/namespace'
+require 'msf/core/modules/metasploit_class_compatibility_error'
require 'msf/core/modules/version_compatibility_error'
# Responsible for loading modules for {Msf::ModuleManager}.
@@ -117,12 +118,17 @@ class Msf::Modules::Loader::Base
metasploit_class = nil
+ module_content = read_module_content(parent_path, type, module_reference_name)
+
+ if module_content.empty?
+ # read_module_content is responsible for calling {#load_error}, so just return here.
+ return false
+ end
+
loaded = namespace_module_transaction(type + "/" + module_reference_name, :reload => reload) { |namespace_module|
# set the parent_path so that the module can be reloaded with #load_module
namespace_module.parent_path = parent_path
- module_content = read_module_content(parent_path, type, module_reference_name)
-
begin
namespace_module.module_eval_with_lexical_scope(module_content, module_path)
# handle interrupts as pass-throughs unlike other Exceptions so users can bail with Ctrl+C
@@ -133,45 +139,33 @@ class Msf::Modules::Loader::Base
begin
namespace_module.version_compatible!(module_path, module_reference_name)
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
- error_message = "Failed to load module (#{module_path}) due to error and #{version_compatibility_error}"
+ load_error(module_path, version_compatibility_error)
else
- error_message = "#{error.class} #{error}"
+ load_error(module_path, error)
end
- # record the error message without the backtrace for the console
- module_manager.module_load_error_by_path[module_path] = error_message
-
- error_message_with_backtrace = "#{error_message}:\n#{error.backtrace.join("\n")}"
- elog(error_message_with_backtrace)
-
return false
end
begin
namespace_module.version_compatible!(module_path, module_reference_name)
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
- error_message = version_compatibility_error.to_s
-
- elog(error_message)
- module_manager.module_load_error_by_path[module_path] = error_message
+ load_error(module_path, version_compatibility_error)
return false
end
- metasploit_class = namespace_module.metasploit_class
+ begin
+ metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
+ rescue Msf::Modules::MetasploitClassCompatibilityError => error
+ load_error(module_path, error)
- unless metasploit_class
- error_message = "Missing Metasploit class constant"
-
- elog(error_message)
- module_manager.module_load_error_by_path[module_path] = error_message
-
- return false
+ return false
end
unless usable?(metasploit_class)
ilog(
- "Skipping module #{module_reference_name} under #{parent_path} because is_usable returned false.",
+ "Skipping module (#{module_reference_name} from #{module_path}) because is_usable returned false.",
'core',
LEV_1
)
@@ -409,6 +403,29 @@ class Msf::Modules::Loader::Base
raise ::NotImplementedError
end
+ # Records the load error to {Msf::ModuleManager::Loading#module_load_error_by_path} and the log.
+ #
+ # @param [String] module_path Path to the module as returned by {#module_path}.
+ # @param [Exception, #class, #to_s, #backtrace] error the error that cause the module not to load.
+ # @return [void]
+ #
+ # @see #module_path
+ def load_error(module_path, error)
+ # module_load_error_by_path does not get the backtrace because the value is echoed to the msfconsole where
+ # backtraces should not appear.
+ module_manager.module_load_error_by_path[module_path] = "#{error.class} #{error}"
+
+ log_lines = []
+ log_lines << "#{module_path} failed to load due to the following error:"
+ log_lines << error.class.to_s
+ log_lines << error.to_s
+ log_lines << "Call stack:"
+ log_lines += error.backtrace
+
+ log_message = log_lines.join("\n")
+ elog(log_message)
+ end
+
# @return [Msf::ModuleManager] The module manager for which this loader is loading modules.
attr_reader :module_manager
diff --git a/lib/msf/core/modules/loader/directory.rb b/lib/msf/core/modules/loader/directory.rb
index a549fdb188..420cbb109e 100644
--- a/lib/msf/core/modules/loader/directory.rb
+++ b/lib/msf/core/modules/loader/directory.rb
@@ -75,13 +75,17 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
module_content = ''
- # force to read in binary mode so Pro modules won't be truncated on Windows
- File.open(full_path, 'rb') do |f|
- # Pass the size of the file as it leads to faster reads due to fewer buffer resizes. Greatest effect on Windows.
- # @see http://www.ruby-forum.com/topic/209005
- # @see https://github.com/ruby/ruby/blob/ruby_1_8_7/io.c#L1205
- # @see https://github.com/ruby/ruby/blob/ruby_1_9_3/io.c#L2038
- module_content = f.read(f.stat.size)
+ begin
+ # force to read in binary mode so Pro modules won't be truncated on Windows
+ File.open(full_path, 'rb') do |f|
+ # Pass the size of the file as it leads to faster reads due to fewer buffer resizes. Greatest effect on Windows.
+ # @see http://www.ruby-forum.com/topic/209005
+ # @see https://github.com/ruby/ruby/blob/ruby_1_8_7/io.c#L1205
+ # @see https://github.com/ruby/ruby/blob/ruby_1_9_3/io.c#L2038
+ module_content = f.read(f.stat.size)
+ end
+ rescue Errno::ENOENT => error
+ load_error(full_path, error)
end
module_content
diff --git a/lib/msf/core/modules/metasploit_class_compatibility_error.rb b/lib/msf/core/modules/metasploit_class_compatibility_error.rb
new file mode 100644
index 0000000000..e5d8193556
--- /dev/null
+++ b/lib/msf/core/modules/metasploit_class_compatibility_error.rb
@@ -0,0 +1,13 @@
+require 'msf/core/modules/error'
+
+# Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant
+# with {Msf::Framework::Major} or lower as a number after 'Metasploit', which indicates a compatible Msf::Module.
+class Msf::Modules::MetasploitClassCompatibilityError < Msf::Modules::Error
+ def initialize(attributes={})
+ super_attributes = {
+ :causal_message => 'Missing compatible Metasploit class constant',
+ }.merge(attributes)
+
+ super(super_attributes)
+ end
+end
\ No newline at end of file
diff --git a/lib/msf/core/modules/namespace.rb b/lib/msf/core/modules/namespace.rb
index 0d5a62a0a0..7841952464 100644
--- a/lib/msf/core/modules/namespace.rb
+++ b/lib/msf/core/modules/namespace.rb
@@ -10,8 +10,6 @@ module Msf::Modules::Namespace
# @return [nil] if such as class is not defined.
def metasploit_class
metasploit_class = nil
- # don't search ancestors for the metasploit_class
- #inherit = false
::Msf::Framework::Major.downto(1) do |major|
# Since we really only care about the deepest namespace, we don't
@@ -29,6 +27,19 @@ module Msf::Modules::Namespace
metasploit_class
end
+ def metasploit_class!(module_path, module_reference_name)
+ metasploit_class = self.metasploit_class
+
+ unless metasploit_class
+ raise Msf::Modules::MetasploitClassCompatibilityError.new(
+ :module_path => module_path,
+ :module_reference_name => module_reference_name
+ )
+ end
+
+ metasploit_class
+ end
+
# Raises an error unless {Msf::Framework::VersionCore} and {Msf::Framework::VersionAPI} meet the minimum required
# versions defined in RequiredVersions in the module content.
#
diff --git a/lib/msf/core/modules/version_compatibility_error.rb b/lib/msf/core/modules/version_compatibility_error.rb
index 00c5341046..638f6d361d 100644
--- a/lib/msf/core/modules/version_compatibility_error.rb
+++ b/lib/msf/core/modules/version_compatibility_error.rb
@@ -1,20 +1,43 @@
+require 'msf/core/modules/error'
+
# Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module}
# if the API or Core version does not meet the minimum requirements defined in the RequiredVersions constant in the
# {Msf::Modules::Loader::Base#read_module_content module content}.
-class Msf::Modules::VersionCompatibilityError < StandardError
+class Msf::Modules::VersionCompatibilityError < Msf::Modules::Error
# @param [Hash{Symbol => Float}] attributes
# @option attributes [Float] :minimum_api_version The minimum {Msf::Framework::VersionAPI} as defined in
# RequiredVersions.
# @option attributes [Float] :minimum_core_version The minimum {Msf::Framework::VersionCore} as defined in
# RequiredVersions.
def initialize(attributes={})
- @module_path = attributes[:module_path]
- @module_reference_name = attributes[:module_reference_name]
@minimum_api_version = attributes[:minimum_api_version]
@minimum_core_version = attributes[:minimum_core_version]
- super("Failed to reload module (#{module_reference_name} from #{module_path}) due to version check " \
- "(requires API:#{minimum_api_version} Core:#{minimum_core_version})")
+ message_parts = []
+ message_parts << 'version check'
+
+ if minimum_api_version or minimum_core_version
+ clause_parts = []
+
+ if minimum_api_version
+ clause_parts << "API >= #{minimum_api_version}"
+ end
+
+ if minimum_core_version
+ clause_parts << "Core >= #{minimum_core_version}"
+ end
+
+ clause = clause_parts.join(' and ')
+ message_parts << "(requires #{clause})"
+ end
+
+ causal_message = message_parts.join(' ')
+
+ super_attributes = {
+ :causal_message => causal_message
+ }.merge(attributes)
+
+ super(super_attributes)
end
# @return [Float] The minimum value of {Msf::Framework::VersionAPI} for the module to be compatible.
diff --git a/modules/auxiliary/scanner/winrm/winrm_wql.rb b/modules/auxiliary/scanner/winrm/winrm_wql.rb
index 31fabd59ec..c93865bb3d 100644
--- a/modules/auxiliary/scanner/winrm/winrm_wql.rb
+++ b/modules/auxiliary/scanner/winrm/winrm_wql.rb
@@ -40,7 +40,8 @@ class Metasploit3 < Msf::Auxiliary
[
OptString.new('WQL', [ true, "The WQL query to run", "Select Name,Status from Win32_Service" ]),
OptString.new('USERNAME', [ true, "The username to authenticate as"]),
- OptString.new('PASSWORD', [ true, "The password to authenticate with"])
+ OptString.new('PASSWORD', [ true, "The password to authenticate with"]),
+ OptString.new('NAMESPACE', [true, 'The WMI namespace to use for queries', '/root/cimv2/'])
], self.class)
end
diff --git a/modules/exploits/windows/winrm/winrm_script_exec.rb b/modules/exploits/windows/winrm/winrm_script_exec.rb
index 32456fea8b..50f66a3aa6 100644
--- a/modules/exploits/windows/winrm/winrm_script_exec.rb
+++ b/modules/exploits/windows/winrm/winrm_script_exec.rb
@@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote
def initialize(info = {})
super(update_info(info,
- 'Name' => 'WinRM VBS Remote Code Execution',
+ 'Name' => 'WinRM Script Exec Remote Code Execution',
'Description' => %q{
This module uses valid credentials to login to the WinRM service
and execute a payload. It has two available methods for payload
diff --git a/modules/post/windows/manage/smart_migrate.rb b/modules/post/windows/manage/smart_migrate.rb
index 598181e7f9..4d1be6749d 100644
--- a/modules/post/windows/manage/smart_migrate.rb
+++ b/modules/post/windows/manage/smart_migrate.rb
@@ -16,7 +16,7 @@ class Metasploit3 < Msf::Post
def initialize(info={})
super( update_info( info,
- 'Name' => 'Windows Manage Process Migration',
+ 'Name' => 'Windows Manage Smart Process Migration',
'Description' => %q{ This module will migrate a Meterpreter session.
It will first attempt to migrate to winlogon.exe . If that fails it will
then look at all of the explorer.exe processes. If there is one that exists
diff --git a/spec/lib/msf/core/modules/error_spec.rb b/spec/lib/msf/core/modules/error_spec.rb
new file mode 100644
index 0000000000..144f485573
--- /dev/null
+++ b/spec/lib/msf/core/modules/error_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe Msf::Modules::Error do
+ context 'instance methods' do
+ context '#initialize' do
+ include_context 'Msf::Modules::Error attributes'
+
+ context 'with :causal_message' do
+ subject do
+ described_class.new(:causal_message => causal_message)
+ end
+
+ it 'should include causal_message in error' do
+ subject.to_s.should == "Failed to load module due to #{causal_message}"
+ end
+ end
+
+ context 'with :causal_message and :module_path' do
+ subject do
+ described_class.new(
+ :causal_message => causal_message,
+ :module_path => module_path
+ )
+ end
+
+ it 'should include causal_message and module_path in error' do
+ subject.to_s.should == "Failed to load module (from #{module_path}) due to #{causal_message}"
+ end
+ end
+
+ context 'with :causal_message and :module_reference_name' do
+ subject do
+ described_class.new(
+ :causal_message => causal_message,
+ :module_reference_name => module_reference_name
+ )
+ end
+
+ it 'should include causal_message and module_reference_name in error' do
+ subject.to_s.should == "Failed to load module (#{module_reference_name}) due to #{causal_message}"
+ end
+ end
+
+ context 'with :causal_message, :module_path, and :module_reference_nam' do
+ subject do
+ described_class.new(
+ :causal_message => causal_message,
+ :module_path => module_path,
+ :module_reference_name => module_reference_name
+ )
+ end
+
+ it 'should include causal_message, module_path, and module_reference_name in error' do
+ subject.to_s.should == "Failed to load module (#{module_reference_name} from #{module_path}) due to #{causal_message}"
+ end
+ end
+
+ context 'with :module_path' do
+ subject do
+ described_class.new(:module_path => module_path)
+ end
+
+ it 'should use :module_path for module_path' do
+ subject.module_path.should == module_path
+ end
+
+ it 'should include module_path in error' do
+ subject.to_s.should == "Failed to load module (from #{module_path})"
+ end
+ end
+
+ context 'with :module_path and :module_reference_name' do
+ subject do
+ described_class.new(
+ :module_path => module_path,
+ :module_reference_name => module_reference_name
+ )
+ end
+
+ it 'should include module_path and module_reference_name in error' do
+ subject.to_s.should == "Failed to load module (#{module_reference_name} from #{module_path})"
+ end
+ end
+
+ context 'with :module_reference_name' do
+ subject do
+ described_class.new(:module_reference_name => module_reference_name)
+ end
+
+ it 'should use :module_reference_name for module_reference_name' do
+ subject.module_reference_name.should == module_reference_name
+ end
+
+ it 'should include module_reference_name in error' do
+ subject.to_s.should == "Failed to load module (#{module_reference_name})"
+ end
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/lib/msf/core/modules/loader/base_spec.rb b/spec/lib/msf/core/modules/loader/base_spec.rb
index df0207d4c4..efce31a1a0 100644
--- a/spec/lib/msf/core/modules/loader/base_spec.rb
+++ b/spec/lib/msf/core/modules/loader/base_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
require 'msf/core'
describe Msf::Modules::Loader::Base do
+ include_context 'Msf::Modules::Loader::Base'
+
let(:described_class_pathname) do
root_pathname.join('lib', 'msf', 'core', 'modules', 'loader', 'base.rb')
end
@@ -37,18 +39,6 @@ describe Msf::Modules::Loader::Base do
'rspec/mock'
end
- let(:parent_path) do
- parent_pathname.to_s
- end
-
- let(:parent_pathname) do
- root_pathname.join('modules')
- end
-
- let(:root_pathname) do
- Pathname.new(Msf::Config.install_root)
- end
-
let(:type) do
Msf::MODULE_AUX
end
@@ -230,7 +220,7 @@ describe Msf::Modules::Loader::Base do
context 'instance methods' do
let(:module_manager) do
- mock('Module Manager')
+ mock('Module Manager', :module_load_error_by_path => {})
end
subject do
@@ -323,13 +313,14 @@ describe Msf::Modules::Loader::Base do
end
it 'should call #namespace_module_transaction with the module full name and :reload => true' do
+ subject.stub(:read_module_content => module_content)
+
subject.should_receive(:namespace_module_transaction).with(module_full_name, hash_including(:reload => true))
subject.load_module(parent_path, type, module_reference_name)
end
it 'should set the parent_path on the namespace_module to match the parent_path passed to #load_module' do
- module_manager.stub(:module_load_error_by_path => {})
module_manager.stub(:on_module_load)
subject.stub(:read_module_content => module_content)
@@ -339,7 +330,6 @@ describe Msf::Modules::Loader::Base do
end
it 'should call #read_module_content to get the module content so that #read_module_content can be overridden to change loading behavior' do
- module_manager.stub(:module_load_error_by_path => {})
module_manager.stub(:on_module_load)
subject.should_receive(:read_module_content).with(parent_path, type, module_reference_name).and_return(module_content)
@@ -348,7 +338,6 @@ describe Msf::Modules::Loader::Base do
it 'should call namespace_module.module_eval_with_lexical_scope with the module_path' do
subject.stub(:read_module_content => malformed_module_content)
- module_manager.stub(:module_load_error_by_path => {})
module_manager.stub(:on_module_load)
# if the module eval error includes the module_path then the module_path was passed along correctly
@@ -356,13 +345,29 @@ describe Msf::Modules::Loader::Base do
subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_false
end
+ context 'with empty module content' do
+ before(:each) do
+ subject.stub(:read_module_content).with(parent_path, type, module_reference_name).and_return('')
+ end
+
+ it 'should return false' do
+ subject.load_module(parent_path, type, module_reference_name).should be_false
+ end
+
+ it 'should not attempt to make a new namespace_module' do
+ subject.should_not_receive(:namespace_module_transaction)
+ subject.load_module(parent_path, type, module_reference_name).should be_false
+ end
+ end
+
context 'with errors from namespace_module_eval_with_lexical_scope' do
before(:each) do
@namespace_module = mock('Namespace Module')
@namespace_module.stub(:parent_path=)
subject.stub(:namespace_module_transaction).and_yield(@namespace_module)
- subject.stub(:read_module_content)
+ module_content = mock('Module Content', :empty? => false)
+ subject.stub(:read_module_content).and_return(module_content)
end
context 'with Interrupt' do
@@ -409,16 +414,8 @@ describe Msf::Modules::Loader::Base do
@namespace_module.stub(:version_compatible!).with(module_path, module_reference_name)
end
- it 'should report error class and string in module_manager.module_load_error_by_path' do
- subject.load_module(parent_path, type, module_reference_name).should be_false
- @module_load_error_by_path[module_path].should == "#{error_class} #{error}"
- end
-
- it 'should report error class, string, and backtrace in the log' do
- subject.should_receive(:elog).with(
- # don't use join on backtrace as that will match implementation too closely
- "#{error_class} #{error}:\n#{backtrace[0]}\n#{backtrace[1]}"
- )
+ it 'should record the load error using the original error' do
+ subject.should_receive(:load_error).with(module_path, error)
subject.load_module(parent_path, type, module_reference_name).should be_false
end
end
@@ -448,18 +445,8 @@ describe Msf::Modules::Loader::Base do
)
end
- it 'should report module_path and version compatibility error string in module_manager.module_load_error_by_path' do
- subject.load_module(parent_path, type, module_reference_name).should be_false
-
- @module_load_error_by_path[module_path].should include(module_path)
- @module_load_error_by_path[module_path].should include(version_compatibility_error.to_s)
- end
-
- it 'should report backtrace of original error in the log' do
- formatted_backtrace = "\n#{backtrace[0]}\n#{backtrace[1]}"
- escaped_backtrace = Regexp.escape(formatted_backtrace)
-
- subject.should_receive(:elog).with(/#{escaped_backtrace}/)
+ it 'should record the load error using the Msf::Modules::VersionCompatibilityError' do
+ subject.should_receive(:load_error).with(module_path, version_compatibility_error)
subject.load_module(parent_path, type, module_reference_name).should be_false
end
end
@@ -479,7 +466,7 @@ describe Msf::Modules::Loader::Base do
@namespace_module.stub(:module_eval_with_lexical_scope).with(module_content, module_path)
metasploit_class = mock('Metasploit Class', :parent => @namespace_module)
- @namespace_module.stub(:metasploit_class => metasploit_class)
+ @namespace_module.stub(:metasploit_class! => metasploit_class)
subject.stub(:namespace_module_transaction).and_yield(@namespace_module)
@@ -521,13 +508,8 @@ describe Msf::Modules::Loader::Base do
)
end
- it 'should report error in module_manage.module_load_error_by_path' do
- subject.load_module(parent_path, type, module_reference_name).should be_false
- @module_load_error_by_path[module_path].should == version_compatibility_error.to_s
- end
-
- it 'should log error' do
- subject.should_receive(:elog).with(version_compatibility_error.to_s)
+ it 'should record the load error' do
+ subject.should_receive(:load_error).with(module_path, version_compatibility_error)
subject.load_module(parent_path, type, module_reference_name).should be_false
end
@@ -548,24 +530,27 @@ describe Msf::Modules::Loader::Base do
end
context 'without metasploit_class' do
+ let(:error) do
+ Msf::Modules::MetasploitClassCompatibilityError.new(
+ :module_path => module_path,
+ :module_reference_name => module_reference_name
+ )
+ end
+
before(:each) do
- @namespace_module.stub(:metasploit_class).and_return(nil)
+ @namespace_module.stub(:metasploit_class!).with(module_path, module_reference_name).and_raise(error)
end
- let(:error_message) do
- 'Missing Metasploit class constant'
- end
-
- it 'should log missing Metasploit class' do
- subject.should_receive(:elog).with(error_message)
+ it 'should record load error' do
+ subject.should_receive(
+ :load_error
+ ).with(
+ module_path,
+ kind_of(Msf::Modules::MetasploitClassCompatibilityError)
+ )
subject.load_module(parent_path, type, module_reference_name).should be_false
end
- it 'should record error in module_manager.module_load_error_by_path' do
- subject.load_module(parent_path, type, module_reference_name).should be_false
- @module_load_error_by_path[module_path].should == error_message
- end
-
it 'should return false' do
subject.load_module(parent_path, type, module_reference_name).should be_false
end
@@ -583,7 +568,7 @@ describe Msf::Modules::Loader::Base do
end
before(:each) do
- @namespace_module.stub(:metasploit_class => metasploit_class)
+ @namespace_module.stub(:metasploit_class! => metasploit_class)
end
it 'should check if it is usable' do
diff --git a/spec/lib/msf/core/modules/loader/directory_spec.rb b/spec/lib/msf/core/modules/loader/directory_spec.rb
index b371883f49..1a1728b21e 100644
--- a/spec/lib/msf/core/modules/loader/directory_spec.rb
+++ b/spec/lib/msf/core/modules/loader/directory_spec.rb
@@ -2,6 +2,126 @@ require 'spec_helper'
require 'msf/core'
require 'msf/core/modules/loader/directory'
-describe Msf::Modules::Loader::Directory do
+require 'msf/core'
+describe Msf::Modules::Loader::Directory do
+ context 'instance methods' do
+ include_context 'Msf::Modules::Loader::Base'
+
+ let(:module_manager) do
+ mock('Module Manager')
+ end
+
+ let(:module_path) do
+ "#{parent_path}/exploits/#{module_reference_name}.rb"
+ end
+
+ let(:type) do
+ 'exploit'
+ end
+
+ subject do
+ described_class.new(module_manager)
+ end
+
+ context '#load_module' do
+ context 'with existent module_path' do
+ let(:framework) do
+ framework = mock('Msf::Framework', :datastore => {})
+
+ events = mock('Events')
+ events.stub(:on_module_load)
+ events.stub(:on_module_created)
+ framework.stub(:events => events)
+
+ framework
+ end
+
+ let(:module_full_name) do
+ "#{type}/#{module_reference_name}"
+ end
+
+ let(:module_manager) do
+ Msf::ModuleManager.new(framework)
+ end
+
+ let(:module_reference_name) do
+ 'windows/smb/ms08_067_netapi'
+ end
+
+ it 'should load a module that can be created' do
+ subject.load_module(parent_path, type, module_reference_name).should be_true
+
+ created_module = module_manager.create(module_full_name)
+
+ created_module.name.should == 'Microsoft Server Service Relative Path Stack Corruption'
+ end
+ end
+
+ context 'without existent module_path' do
+ let(:module_reference_name) do
+ 'osx/armle/safari_libtiff'
+ end
+
+ let(:error) do
+ Errno::ENOENT.new(module_path)
+ end
+
+ before(:each) do
+ module_manager.stub(:file_changed? => true)
+ module_manager.stub(:module_load_error_by_path => {})
+ end
+
+ it 'should not raise an error' do
+ File.exist?(module_path).should be_false
+
+ expect {
+ subject.load_module(parent_path, type, module_reference_name)
+ }.to_not raise_error
+ end
+
+ it 'should return false' do
+ File.exist?(module_path).should be_false
+
+ subject.load_module(parent_path, type, module_reference_name).should be_false
+ end
+ end
+ end
+
+ context '#read_module_content' do
+ context 'with non-existent module_path' do
+ let(:module_reference_name) do
+ 'osx/armle/safari_libtiff'
+ end
+
+ before(:each) do
+ subject.stub(:load_error).with(module_path, kind_of(Errno::ENOENT))
+ end
+
+ # this ensures that the File.exist?(module_path) checks are checking the same path as the code under test
+ it 'should attempt to open the expected module_path' do
+ File.should_receive(:open).with(module_path, 'rb')
+ File.exist?(module_path).should be_false
+
+ subject.send(:read_module_content, parent_path, type, module_reference_name)
+ end
+
+ it 'should not raise an error' do
+ expect {
+ subject.send(:read_module_content, parent_path, type, module_reference_name)
+ }.to_not raise_error
+ end
+
+ it 'should return an empty string' do
+ subject.send(:read_module_content, parent_path, type, module_reference_name).should == ''
+ end
+
+ it 'should record the load error' do
+ subject.should_receive(:load_error).with(module_path, kind_of(Errno::ENOENT))
+
+ subject.send(:read_module_content, parent_path, type, module_reference_name).should == ''
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/msf/core/modules/metasploit_class_compatibility_error_spec.rb b/spec/lib/msf/core/modules/metasploit_class_compatibility_error_spec.rb
new file mode 100644
index 0000000000..b3a411a7d6
--- /dev/null
+++ b/spec/lib/msf/core/modules/metasploit_class_compatibility_error_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+require 'msf/core/modules/metasploit_class_compatibility_error'
+
+describe Msf::Modules::MetasploitClassCompatibilityError do
+ it_should_behave_like 'Msf::Modules::Error subclass #initialize'
+end
\ No newline at end of file
diff --git a/spec/lib/msf/core/modules/namespace_spec.rb b/spec/lib/msf/core/modules/namespace_spec.rb
new file mode 100644
index 0000000000..10f10822f6
--- /dev/null
+++ b/spec/lib/msf/core/modules/namespace_spec.rb
@@ -0,0 +1,267 @@
+require 'spec_helper'
+
+require 'msf/core'
+require 'msf/core/modules/namespace'
+
+describe Msf::Modules::Namespace do
+ let(:module_path) do
+ "parent/path/type_directory/#{module_reference_name}.rb"
+ end
+
+ let(:module_reference_name) do
+ 'module/reference/name'
+ end
+
+ subject do
+ mod = Module.new
+ mod.extend described_class
+
+ mod
+ end
+
+ context 'metasploit_class' do
+ before(:each) do
+ if major
+ subject.const_set("Metasploit#{major}", Class.new)
+ end
+ end
+
+ context 'without Metasploit constant defined' do
+ let(:major) do
+ nil
+ end
+
+ it 'should not be defined' do
+ metasploit_constants = subject.constants.select { |constant|
+ constant.to_s =~ /Metasploit/
+ }
+
+ metasploit_constants.should be_empty
+ end
+ end
+
+ context 'with Metasploit1 constant defined' do
+ let(:major) do
+ 1
+ end
+
+ it 'should be defined' do
+ subject.const_defined?('Metasploit1').should be_true
+ end
+
+ it 'should return the class' do
+ subject.metasploit_class.should be_a Class
+ end
+ end
+
+ context 'with Metasploit2 constant defined' do
+ let(:major) do
+ 2
+ end
+
+ it 'should be defined' do
+ subject.const_defined?('Metasploit2').should be_true
+ end
+
+ it 'should return the class' do
+ subject.metasploit_class.should be_a Class
+ end
+ end
+
+ context 'with Metasploit3 constant defined' do
+ let(:major) do
+ 3
+ end
+
+ it 'should be defined' do
+ subject.const_defined?('Metasploit3').should be_true
+ end
+
+ it 'should return the class' do
+ subject.metasploit_class.should be_a Class
+ end
+ end
+
+ context 'with Metasploit4 constant defined' do
+ let(:major) do
+ 4
+ end
+
+ it 'should be defined' do
+ subject.const_defined?('Metasploit4').should be_true
+ end
+
+ it 'should return the class' do
+ subject.metasploit_class.should be_a Class
+ end
+ end
+
+ context 'with Metasploit5 constant defined' do
+ let(:major) do
+ 5
+ end
+
+ it 'should be defined' do
+ subject.const_defined?('Metasploit5').should be_true
+ end
+
+ it 'should be newer than Msf::Framework::Major' do
+ major.should > Msf::Framework::Major
+ end
+
+ it 'should return nil' do
+ subject.metasploit_class.should be_nil
+ end
+ end
+ end
+
+ context 'metasploit_class!' do
+ it 'should call metasploit_class' do
+ subject.should_receive(:metasploit_class).and_return(Class.new)
+
+ subject.metasploit_class!(module_path, module_reference_name)
+ end
+
+ context 'with metasploit_class' do
+ let(:metasploit_class) do
+ Class.new
+ end
+
+ before(:each) do
+ subject.stub(:metasploit_class => metasploit_class)
+ end
+
+ it 'should return the metasploit_class' do
+ subject.metasploit_class!(module_path, module_reference_name).should == metasploit_class
+ end
+ end
+
+ context 'without metasploit_class' do
+ before(:each) do
+ subject.stub(:metasploit_class => nil)
+ end
+
+ it 'should raise a Msf::Modules::MetasploitClassCompatibilityError' do
+ expect {
+ subject.metasploit_class!(module_path, module_reference_name)
+ }.to raise_error(Msf::Modules::MetasploitClassCompatibilityError)
+ end
+
+ context 'the Msf::Modules::MetasploitClassCompatibilityError' do
+ it 'should include the module path' do
+ error = nil
+
+ begin
+ subject.metasploit_class!(module_path, module_reference_name)
+ rescue Msf::Modules::MetasploitClassCompatibilityError => error
+ end
+
+ error.should_not be_nil
+ error.to_s.should include(module_path)
+ end
+
+ it 'should include the module reference name' do
+ error = nil
+
+ begin
+ subject.metasploit_class!(module_path, module_reference_name)
+ rescue Msf::Modules::MetasploitClassCompatibilityError => error
+ end
+
+ error.should_not be_nil
+ error.to_s.should include(module_reference_name)
+ end
+ end
+ end
+ end
+
+ context 'version_compatible!' do
+ context 'without RequiredVersions' do
+ it 'should not be defined' do
+ subject.const_defined?('RequiredVersions').should be_false
+ end
+
+ it 'should not raise an error' do
+ expect {
+ subject.version_compatible!(module_path, module_reference_name)
+ }.to_not raise_error
+ end
+ end
+
+ context 'with RequiredVersions defined' do
+ let(:minimum_api_version) do
+ 1
+ end
+
+ let(:minimum_core_version) do
+ 1
+ end
+
+ before(:each) do
+ subject.const_set(
+ :RequiredVersions,
+ [
+ minimum_core_version,
+ minimum_api_version
+ ]
+ )
+ end
+
+ context 'with minimum Core version' do
+ it 'should be <= Msf::Framework::VersionCore' do
+ minimum_core_version.should <= Msf::Framework::VersionCore
+ end
+
+ context 'without minimum API version' do
+ let(:minimum_api_version) do
+ 2
+ end
+
+ it 'should be > Msf::Framework::VersionAPI' do
+ minimum_api_version.should > Msf::Framework::VersionAPI
+ end
+
+ it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
+ end
+
+ context 'with minimum API version' do
+ it 'should not raise an error' do
+ expect {
+ subject.version_compatible!(module_path, module_reference_name)
+ }.to_not raise_error(Msf::Modules::VersionCompatibilityError)
+ end
+ end
+ end
+
+ context 'without minimum Core version' do
+ let(:minimum_core_version) do
+ 5
+ end
+
+ it 'should be > Msf::Framework::VersionCore' do
+ minimum_core_version.should > Msf::Framework::VersionCore
+ end
+
+ context 'without minimum API version' do
+ let(:minimum_api_version) do
+ 2
+ end
+
+ it 'should be > Msf::Framework::VersionAPI' do
+ minimum_api_version.should > Msf::Framework::VersionAPI
+ end
+
+ it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
+ end
+
+ context 'with minimum API version' do
+ it 'should be <= Msf::Framework::VersionAPI' do
+ minimum_api_version <= Msf::Framework::VersionAPI
+ end
+
+ it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/lib/msf/core/modules/version_compatibility_error_spec.rb b/spec/lib/msf/core/modules/version_compatibility_error_spec.rb
new file mode 100644
index 0000000000..9522f8bbf7
--- /dev/null
+++ b/spec/lib/msf/core/modules/version_compatibility_error_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe Msf::Modules::VersionCompatibilityError do
+ it_should_behave_like 'Msf::Modules::Error subclass #initialize' do
+ let(:minimum_api_version) do
+ 1
+ end
+
+ let(:minimum_core_version) do
+ 2
+ end
+
+ it 'should say cause was version check' do
+ subject.to_s.should match(/due to version check/)
+ end
+
+ context 'with :minimum_api_version' do
+ subject do
+ described_class.new(
+ :minimum_api_version => minimum_api_version
+ )
+ end
+
+ it 'should set minimum_api_version' do
+ subject.minimum_api_version.should == minimum_api_version
+ end
+
+ it 'should include minimum_api_version in error' do
+ subject.to_s.should match(/due to version check \(requires API >= #{minimum_api_version}\)/)
+ end
+ end
+
+ context 'with :minimum_api_version and :minimum_core_version' do
+ subject do
+ described_class.new(
+ :minimum_api_version => minimum_api_version,
+ :minimum_core_version => minimum_core_version
+ )
+ end
+
+ it 'should include minimum_api_version and minimum_core_version in error' do
+ subject.to_s.should match(/due to version check \(requires API >= #{minimum_api_version} and Core >= #{minimum_core_version}\)/)
+ end
+ end
+
+ context 'with :minimum_core_version' do
+ subject do
+ described_class.new(
+ :minimum_core_version => minimum_core_version
+ )
+ end
+
+ it 'should set minimum_core_version' do
+ subject.minimum_core_version.should == minimum_core_version
+ end
+
+ it 'should include minimum_core_version in error' do
+ subject.to_s.should match(/due to version check \(requires Core >= #{minimum_core_version}\)/)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index f62338294f..6f8932bc27 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -8,6 +8,11 @@ root_pathname = spec_pathname.join('..').expand_path
lib_pathname = root_pathname.join('lib')
$LOAD_PATH.unshift(lib_pathname.to_s)
+# must be first require and started before any other requires so that it can measure coverage of all following required
+# code. It is after the rubygems and bundler only because Bundler.setup supplies the LOAD_PATH to simplecov.
+require 'simplecov'
+SimpleCov.start
+
require 'rspec/core'
# Requires supporting ruby files with custom matchers and macros, etc,
diff --git a/spec/support/shared/contexts/msf_modules_error_attributes.rb b/spec/support/shared/contexts/msf_modules_error_attributes.rb
new file mode 100644
index 0000000000..74d6366f58
--- /dev/null
+++ b/spec/support/shared/contexts/msf_modules_error_attributes.rb
@@ -0,0 +1,13 @@
+shared_context 'Msf::Modules::Error attributes' do
+ let(:causal_message) do
+ 'rspec'
+ end
+
+ let(:module_path) do
+ "parent/path/type/#{module_reference_name}.rb"
+ end
+
+ let(:module_reference_name) do
+ 'module/reference/name'
+ end
+end
\ No newline at end of file
diff --git a/spec/support/shared/contexts/msf_modules_loader_base.rb b/spec/support/shared/contexts/msf_modules_loader_base.rb
new file mode 100644
index 0000000000..27d4a4e56a
--- /dev/null
+++ b/spec/support/shared/contexts/msf_modules_loader_base.rb
@@ -0,0 +1,13 @@
+shared_context "Msf::Modules::Loader::Base" do
+ let(:parent_path) do
+ parent_pathname.to_s
+ end
+
+ let(:parent_pathname) do
+ root_pathname.join('modules')
+ end
+
+ let(:root_pathname) do
+ Pathname.new(Msf::Config.install_root)
+ end
+end
\ No newline at end of file
diff --git a/spec/support/shared/examples/msf_modules_error_subclass_initialize.rb b/spec/support/shared/examples/msf_modules_error_subclass_initialize.rb
new file mode 100644
index 0000000000..43a753a044
--- /dev/null
+++ b/spec/support/shared/examples/msf_modules_error_subclass_initialize.rb
@@ -0,0 +1,26 @@
+shared_examples_for 'Msf::Modules::Error subclass #initialize' do
+ context 'instance methods' do
+ context '#initialize' do
+ include_context 'Msf::Modules::Error attributes'
+
+ subject do
+ described_class.new(
+ :module_path => module_path,
+ :module_reference_name => module_reference_name
+ )
+ end
+
+ it 'should include causal message in error' do
+ subject.to_s.should match(/due to .*/)
+ end
+
+ it 'should set module_path' do
+ subject.module_path.should == module_path
+ end
+
+ it 'should set module_reference_name' do
+ subject.module_reference_name.should == module_reference_name
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/support/shared/examples/msf_modules_version_compatibility_error.rb b/spec/support/shared/examples/msf_modules_version_compatibility_error.rb
new file mode 100644
index 0000000000..a2f11c7924
--- /dev/null
+++ b/spec/support/shared/examples/msf_modules_version_compatibility_error.rb
@@ -0,0 +1,32 @@
+shared_examples_for 'Msf::Modules::VersionCompatibilityError' do
+ let(:error) do
+ begin
+ subject.version_compatible!(module_path, module_reference_name)
+ rescue Msf::Modules::VersionCompatibilityError => error
+ end
+
+ error
+ end
+
+ it 'should be raised' do
+ expect {
+ subject.version_compatible!(module_path, module_reference_name)
+ }.to raise_error(Msf::Modules::VersionCompatibilityError)
+ end
+
+ it 'should include minimum API version' do
+ error.to_s.should include(minimum_api_version.to_s)
+ end
+
+ it 'should include minimum Core version' do
+ error.to_s.should include(minimum_core_version.to_s)
+ end
+
+ it 'should include module path' do
+ error.to_s.should include(module_path)
+ end
+
+ it 'should include module reference name' do
+ error.to_s.should include(module_reference_name)
+ end
+end
\ No newline at end of file